@taskforcehq/taskforce 0.3.301 → 0.3.302

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 (147) hide show
  1. package/dist/Taskforce.module.css +26 -2
  2. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  3. package/dist/TaskforceCore.js +13 -24
  4. package/dist/TaskforceCore.test.js +167 -152
  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/ui/ReferenceBadgeButton.js +1 -1
  14. package/dist/components/views/PlansPage.js +9 -8
  15. package/dist/components/views/StandaloneLayout.js +466 -204
  16. package/dist/components/views/panels/PlanningDrawer.js +18 -1
  17. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
  18. package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
  19. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  20. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  21. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  22. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  23. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  24. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  25. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  26. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  27. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  28. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  29. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  30. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  31. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  32. package/dist/components/views/standalone/modals/types.js +1 -0
  33. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  34. package/dist/components/views/teamManagementAccess.js +2 -2
  35. package/dist/components/views/teamManagementAccess.test.js +3 -3
  36. package/dist/config/deployment.js +2 -2
  37. package/dist/config/deployment.test.js +5 -3
  38. package/dist/core/AiProfileService.js +7 -2
  39. package/dist/core/AiProfiles.test.js +19 -0
  40. package/dist/core/SyncReconciliationService.d.ts +2 -0
  41. package/dist/core/SyncReconciliationService.js +3 -0
  42. package/dist/core/TaskAuditTimeline.test.js +40 -0
  43. package/dist/core/Taskforce.d.ts +49 -0
  44. package/dist/core/Taskforce.ids.test.js +133 -0
  45. package/dist/core/Taskforce.js +241 -78
  46. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  47. package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
  48. package/dist/core/taskReferencePolicy.d.ts +28 -0
  49. package/dist/core/taskReferencePolicy.js +189 -0
  50. package/dist/core/types.d.ts +2 -0
  51. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  52. package/dist/hooks/useTaskData.js +3 -2
  53. package/dist/hooks/useTaskData.test.js +66 -0
  54. package/dist/hooks/useTaskforce.d.ts +5 -0
  55. package/dist/hooks/useTaskforce.js +63 -15
  56. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  57. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  58. package/dist/mcp/aiProfileBootstrap.js +80 -0
  59. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  60. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  61. package/dist/mcp/clientIntegrations.d.ts +2 -8
  62. package/dist/mcp/clientIntegrations.js +21 -40
  63. package/dist/mcp/clientIntegrations.test.js +28 -2
  64. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  65. package/dist/mcp/clientProfileDefaults.js +37 -0
  66. package/dist/mcp/runtime.d.ts +10 -7
  67. package/dist/mcp/runtime.js +41 -19
  68. package/dist/mcp/runtime.test.js +49 -2
  69. package/dist/migrations/taskSchemaMigrations.js +53 -1
  70. package/dist/qaOpen.test.d.ts +1 -0
  71. package/dist/qaOpen.test.js +56 -0
  72. package/dist/server/index.cookieProxy.test.js +1 -0
  73. package/dist/server/index.d.ts +1 -0
  74. package/dist/server/index.js +15 -1
  75. package/dist/server/index.test.js +14 -1
  76. package/dist/server/routes/admin.js +25 -22
  77. package/dist/server/routes/auth.d.ts +5 -0
  78. package/dist/server/routes/auth.js +350 -16
  79. package/dist/server/routes/billing.js +11 -1
  80. package/dist/server/routes/billing.test.js +32 -2
  81. package/dist/server/routes/documents.js +6 -1
  82. package/dist/server/routes/shared.d.ts +1 -1
  83. package/dist/server/routes/shared.js +11 -3
  84. package/dist/server/routes/sync.integration.test.js +130 -0
  85. package/dist/server/routes/sync.js +226 -1924
  86. package/dist/server/routes/tasks.js +18 -4
  87. package/dist/server/routes.js +26 -19
  88. package/dist/server/routes.test.js +352 -6
  89. package/dist/shared/runtimeContract.d.ts +1 -1
  90. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  91. package/dist/sync/collaborationSyncPayload.js +131 -0
  92. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  93. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  94. package/dist/sync/collaborationSyncState.d.ts +15 -0
  95. package/dist/sync/collaborationSyncState.js +60 -0
  96. package/dist/sync/contentSyncPayload.d.ts +45 -0
  97. package/dist/sync/contentSyncPayload.js +104 -0
  98. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  99. package/dist/sync/contentSyncPayload.test.js +104 -0
  100. package/dist/sync/contentSyncState.d.ts +88 -0
  101. package/dist/sync/contentSyncState.js +743 -0
  102. package/dist/sync/planningSyncPayload.d.ts +9 -0
  103. package/dist/sync/planningSyncPayload.js +82 -0
  104. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  105. package/dist/sync/planningSyncPayload.test.js +68 -0
  106. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  107. package/dist/sync/syncApplyHandlers.js +445 -0
  108. package/dist/sync/taskSyncPayload.d.ts +33 -0
  109. package/dist/sync/taskSyncPayload.js +126 -0
  110. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  111. package/dist/sync/taskSyncPayload.test.js +54 -0
  112. package/dist/sync/workspacePullFeed.d.ts +28 -0
  113. package/dist/sync/workspacePullFeed.js +301 -0
  114. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  115. package/dist/sync/workspacePullFeed.test.js +166 -0
  116. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  117. package/dist/sync/workspaceSyncModel.js +9 -2
  118. package/dist/sync/workspaceSyncModel.test.js +29 -0
  119. package/dist/test/setup.js +1 -1
  120. package/dist/types.d.ts +1 -0
  121. package/dist/ui/assets/{AgentsModule-Bdq3T1Ts.js → AgentsModule-JG5jc3he.js} +1 -1
  122. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-jKUVo-PJ.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
  123. package/dist/ui/assets/{DocumentWorkspace-DuQa-uIC.js → DocumentWorkspace-iys3zfca.js} +2 -2
  124. package/dist/ui/assets/{InitiativesModule-C8Pi2VTk.js → InitiativesModule-DJvLJI3A.js} +1 -1
  125. package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
  126. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
  127. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  128. package/dist/ui/assets/{WorkflowsModule-b6TLuHwJ.js → WorkflowsModule-BguHeQaV.js} +1 -1
  129. package/dist/ui/assets/index-MXeWoebC.css +1 -0
  130. package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
  131. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
  132. package/dist/ui/index.html +3 -3
  133. package/dist/utils/billingStatusCache.d.ts +21 -0
  134. package/dist/utils/billingStatusCache.js +70 -0
  135. package/dist/utils/taskActivity.js +17 -4
  136. package/dist/utils/taskActivity.test.js +27 -0
  137. package/dist/utils/taskNormalization.js +10 -2
  138. package/dist/utils/taskReferences.d.ts +11 -1
  139. package/dist/utils/taskReferences.js +33 -1
  140. package/package.json +2 -1
  141. package/scripts/qa-open-lib.mjs +175 -0
  142. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  143. package/scripts/qa-open.mjs +352 -0
  144. package/dist/ui/assets/PlansPage-B6BDECSi.js +0 -1
  145. package/dist/ui/assets/TaskSettings-DUJgRTPU.js +0 -8
  146. package/dist/ui/assets/index-BiiF1cLQ.css +0 -1
  147. 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, 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 { invalidateBillingStatusCache, loadBillingStatusCached } from '../../utils/billingStatusCache';
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,8 @@ 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;
51
61
  function getSyncEventStableKey(event, index = 0) {
52
62
  const explicitId = String(event.id ?? '').trim();
53
63
  if (explicitId)
@@ -147,6 +157,9 @@ function isDebugModeEnabled() {
147
157
  return runtimeOverride;
148
158
  return typeof __DEBUG_MODE__ !== 'undefined' && __DEBUG_MODE__;
149
159
  }
160
+ function getTimingNow() {
161
+ return typeof performance !== 'undefined' ? performance.now() : Date.now();
162
+ }
150
163
  export function StandaloneLayout(props) {
151
164
  const WORKSPACE_TOOL_RAIL_WIDTH = 56;
152
165
  const WORKSPACE_CONTENT_GUTTER = 6;
@@ -202,7 +215,7 @@ export function StandaloneLayout(props) {
202
215
  // clearFilters, // This was duplicated, removed one instance
203
216
  // activeTypes, // This was duplicated, removed one instance
204
217
  // 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,
218
+ 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, logout, projectRoot, projectName, mcpScriptPath, serverHostRoot,
206
219
  // Browser Props
207
220
  showFolderBrowser, setShowFolderBrowser, folders, files, currentBrowsePath, fetchFolders, browserTarget, setBrowserTarget, handleSelectPath, handleAddPath, handleRemovePath,
208
221
  // Settings Managers
@@ -430,6 +443,15 @@ export function StandaloneLayout(props) {
430
443
  if (typeof state.planningDrawerOpen === 'boolean') {
431
444
  setPlanningDrawerOpen(state.planningDrawerOpen);
432
445
  }
446
+ if (typeof state.planningTreeCollapsed === 'boolean') {
447
+ setPlanningTreeCollapsed(state.planningTreeCollapsed);
448
+ }
449
+ if (typeof state.planningPrimaryCollapsed === 'boolean') {
450
+ setPlanningPrimaryCollapsed(state.planningPrimaryCollapsed);
451
+ }
452
+ if (typeof state.planningSecondaryCollapsed === 'boolean') {
453
+ setPlanningSecondaryCollapsed(state.planningSecondaryCollapsed);
454
+ }
433
455
  if (Array.isArray(state.planningExpandedInitiativeIds)) {
434
456
  setExpandedPlanningInitiativeIds(new Set(state.planningExpandedInitiativeIds
435
457
  .map((value) => String(value || '').trim())
@@ -499,6 +521,9 @@ export function StandaloneLayout(props) {
499
521
  documentTypeFilters,
500
522
  documentAttachmentFilters,
501
523
  planningDrawerOpen,
524
+ planningTreeCollapsed,
525
+ planningPrimaryCollapsed,
526
+ planningSecondaryCollapsed,
502
527
  planningExpandedInitiativeIds: Array.from(expandedPlanningInitiativeIds),
503
528
  planningDrawerDetail,
504
529
  planningNestedWorkstreamDetailId,
@@ -526,6 +551,9 @@ export function StandaloneLayout(props) {
526
551
  documentTypeFilters,
527
552
  documentAttachmentFilters,
528
553
  planningDrawerOpen,
554
+ planningTreeCollapsed,
555
+ planningPrimaryCollapsed,
556
+ planningSecondaryCollapsed,
529
557
  expandedPlanningInitiativeIds,
530
558
  planningDrawerDetail,
531
559
  planningNestedWorkstreamDetailId,
@@ -802,16 +830,11 @@ export function StandaloneLayout(props) {
802
830
  return map;
803
831
  }, [assigneeOptions]);
804
832
  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);
833
+ const planningScopedTasks = taskScope === 'archived'
834
+ ? archivedScopeAllTasks
835
+ : taskScope === 'deleted'
836
+ ? deletedScopeAllTasks
837
+ : props.tasks.filter((task) => !task.isArchived);
815
838
  const summarizeTasks = (scopeTasks) => {
816
839
  const taskCount = scopeTasks.length;
817
840
  const completedTaskCount = scopeTasks.filter((task) => task.isArchived || task.status === 'done' || task.status === 'cancelled').length;
@@ -827,7 +850,7 @@ export function StandaloneLayout(props) {
827
850
  const workstreamById = new Map();
828
851
  const initiativeById = new Map();
829
852
  const buildWorkstreamSummary = (workstream) => {
830
- const scopedTasks = hierarchyTasks.filter((task) => (task.workstreamId || null) === workstream.id);
853
+ const scopedTasks = planningScopedTasks.filter((task) => (task.workstreamId || null) === workstream.id);
831
854
  const taskPreviews = scopedTasks.map((task) => ({
832
855
  id: task.id,
833
856
  referenceNumber: task.referenceNumber ?? null,
@@ -853,10 +876,10 @@ export function StandaloneLayout(props) {
853
876
  workstreamById.set(summary.id, summary);
854
877
  return summary;
855
878
  };
856
- const initiatives = planningInitiatives.map((initiative) => {
857
- const workstreams = planningWorkstreams
858
- .filter((workstream) => (workstream.initiativeId || null) === initiative.id)
859
- .map(buildWorkstreamSummary);
879
+ const allWorkstreamSummaries = props.workstreams.map(buildWorkstreamSummary);
880
+ const allInitiatives = props.initiatives.map((initiative) => {
881
+ const workstreams = allWorkstreamSummaries
882
+ .filter((workstream) => (workstream.initiativeId || null) === initiative.id);
860
883
  const initiativeScopedTasks = workstreams
861
884
  .flatMap((workstream) => workstreamTaskIds.get(workstream.id) || [])
862
885
  .map((taskId) => hierarchyTaskById.get(taskId))
@@ -878,9 +901,18 @@ export function StandaloneLayout(props) {
878
901
  initiativeById.set(initiativeSummary.id, initiativeSummary);
879
902
  return initiativeSummary;
880
903
  });
881
- const standaloneWorkstreams = planningWorkstreams
904
+ const initiatives = taskScope === 'deleted'
905
+ ? []
906
+ : allInitiatives.filter((initiative) => taskScope === 'archived'
907
+ ? Boolean(initiative.isArchived)
908
+ : !initiative.isArchived);
909
+ const standaloneWorkstreams = allWorkstreamSummaries
882
910
  .filter((workstream) => !workstream.initiativeId)
883
- .map(buildWorkstreamSummary);
911
+ .filter((workstream) => taskScope === 'deleted'
912
+ ? false
913
+ : taskScope === 'archived'
914
+ ? Boolean(workstream.isArchived)
915
+ : !workstream.isArchived);
884
916
  return {
885
917
  initiatives,
886
918
  standaloneWorkstreams,
@@ -890,7 +922,7 @@ export function StandaloneLayout(props) {
890
922
  workstreamById,
891
923
  initiativeById,
892
924
  };
893
- }, [assigneeLabelByValue, hierarchyTaskById, hierarchyTasks, props.initiatives, props.workstreams, taskScope]);
925
+ }, [archivedScopeAllTasks, assigneeLabelByValue, deletedScopeAllTasks, hierarchyTaskById, props.initiatives, props.tasks, props.workstreams, taskScope]);
894
926
  const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
895
927
  const referenceLabel = formatInitiativeReference(initiative);
896
928
  return {
@@ -962,13 +994,6 @@ export function StandaloneLayout(props) {
962
994
  return exactInitiative;
963
995
  return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
964
996
  }, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
965
- useEffect(() => {
966
- if (!planningDrawerOpen)
967
- return;
968
- setPlanningTreeCollapsed(false);
969
- setPlanningPrimaryCollapsed(false);
970
- setPlanningSecondaryCollapsed(false);
971
- }, [planningDrawerOpen]);
972
997
  useEffect(() => {
973
998
  if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
974
999
  setSelectedPlanningInitiativeId('');
@@ -976,23 +1001,7 @@ export function StandaloneLayout(props) {
976
1001
  if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
977
1002
  setSelectedPlanningWorkstreamId('');
978
1003
  }
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
1004
  }, [
993
- planningDrawerDetail,
994
- planningNestedWorkstreamDetailId,
995
- planningSecondaryEditor,
996
1005
  planningStructure.initiativeById,
997
1006
  planningStructure.workstreamById,
998
1007
  selectedPlanningInitiativeId,
@@ -1059,6 +1068,29 @@ export function StandaloneLayout(props) {
1059
1068
  return !!task.dueDate && task.dueDate < todayDateOnly;
1060
1069
  }).length;
1061
1070
  }, [groupBy, hierarchyVisibleTasks, todayDateOnly]);
1071
+ const normalizedAuthUserId = String(authUserId || '').trim();
1072
+ const assignedTaskCount = useMemo(() => {
1073
+ if (!normalizedAuthUserId || normalizedAuthUserId === 'anonymous')
1074
+ return 0;
1075
+ return tasks.filter((task) => {
1076
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
1077
+ if (isTerminal)
1078
+ return false;
1079
+ return String(task.assignee || '').trim() === normalizedAuthUserId;
1080
+ }).length;
1081
+ }, [normalizedAuthUserId, tasks]);
1082
+ const assignedOverdueTaskCount = useMemo(() => {
1083
+ if (!normalizedAuthUserId || normalizedAuthUserId === 'anonymous')
1084
+ return 0;
1085
+ return tasks.filter((task) => {
1086
+ const isTerminal = task.status === 'done' || task.status === 'cancelled';
1087
+ if (isTerminal)
1088
+ return false;
1089
+ if (String(task.assignee || '').trim() !== normalizedAuthUserId)
1090
+ return false;
1091
+ return !!task.dueDate && task.dueDate < todayDateOnly;
1092
+ }).length;
1093
+ }, [normalizedAuthUserId, tasks, todayDateOnly]);
1062
1094
  const expiredColumnCandidates = useMemo(() => {
1063
1095
  if (groupBy !== 'schedule')
1064
1096
  return [];
@@ -1168,7 +1200,10 @@ export function StandaloneLayout(props) {
1168
1200
  const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
1169
1201
  const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
1170
1202
  const [teamManagementTab, setTeamManagementTab] = useState('members');
1203
+ const [teamWorkspaceRole, setTeamWorkspaceRole] = useState(null);
1171
1204
  const [teamPlanMode, setTeamPlanMode] = useState('unknown');
1205
+ const [teamManagementAccessLoading, setTeamManagementAccessLoading] = useState(false);
1206
+ const [teamManagementAccessError, setTeamManagementAccessError] = useState(null);
1172
1207
  const [teamMgmtError, setTeamMgmtError] = useState(null);
1173
1208
  const [teamUsersLoading, setTeamUsersLoading] = useState(false);
1174
1209
  const [teamUsers, setTeamUsers] = useState([]);
@@ -1191,11 +1226,20 @@ export function StandaloneLayout(props) {
1191
1226
  const [billingNotice, setBillingNotice] = useState(null);
1192
1227
  const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
1193
1228
  const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
1229
+ const [profileAvatarPreviewUrl, setProfileAvatarPreviewUrl] = useState('');
1230
+ const [profileAvatarDraftId, setProfileAvatarDraftId] = useState(null);
1231
+ const [profileAvatarBusy, setProfileAvatarBusy] = useState(false);
1194
1232
  const [profileSaveBusy, setProfileSaveBusy] = useState(false);
1195
1233
  const [profileSaveError, setProfileSaveError] = useState(null);
1196
1234
  const [profileSaveNotice, setProfileSaveNotice] = useState(null);
1197
1235
  const TEAM_AUDIT_PAGE_SIZE = 25;
1198
1236
  const accountMenuRef = useRef(null);
1237
+ const profileAvatarInputRef = useRef(null);
1238
+ const accountSurfaceOpenStartedAtRef = useRef(null);
1239
+ const accountSurfacePendingRef = useRef({
1240
+ billing: false,
1241
+ teamManagement: false
1242
+ });
1199
1243
  // showSearch state removed - search always visible in actions area
1200
1244
  useEffect(() => {
1201
1245
  if (!showAccountMenu)
@@ -1239,20 +1283,29 @@ export function StandaloneLayout(props) {
1239
1283
  const fallbackId = String(currentWorkspaceId || '').trim();
1240
1284
  return fallbackId || 'Workspace';
1241
1285
  }, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
1242
- const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, currentWorkspaceRole);
1243
- const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, currentWorkspaceRole, teamPlanMode);
1286
+ const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, teamWorkspaceRole);
1287
+ const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, teamWorkspaceRole, teamPlanMode);
1244
1288
  const authDisplayName = String(authUserDisplayName || '').trim();
1289
+ const authAvatarUrl = String(authUserAvatarUrl || '').trim();
1245
1290
  const authEmailLabel = String(authUserEmail || '').trim();
1246
1291
  const authIdentityLabel = authDisplayName || authEmailLabel;
1247
1292
  const hasAuthIdentity = authIdentityLabel.length > 0;
1248
1293
  const hasAuthEmail = authEmailLabel.length > 0;
1249
1294
  const accountBadgeLabel = (authIdentityLabel || authEmailLabel || 'U').trim();
1250
1295
  const accountBadgeInitial = accountBadgeLabel.charAt(0).toUpperCase() || 'U';
1296
+ const accountAvatarUrl = authAvatarUrl;
1297
+ const profileAvatarDisplayUrl = String(profileAvatarPreviewUrl || authAvatarUrl).trim();
1251
1298
  const subscriptionLabel = String(billingStatus?.planName || billingStatus?.planId || '').trim();
1252
1299
  const hasSubscription = subscriptionLabel.length > 0;
1253
1300
  const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
1254
1301
  const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
1255
1302
  const apiBaseUrl = props.config?.apiBaseUrl || '';
1303
+ const accountCapabilityApiBaseUrl = useMemo(() => {
1304
+ const preferredBase = runtimeMode === 'local'
1305
+ ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1306
+ : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1307
+ return String(preferredBase || '').trim().replace(/\/+$/, '');
1308
+ }, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1256
1309
  const billingApiBaseUrl = useMemo(() => {
1257
1310
  const preferredBase = runtimeMode === 'local'
1258
1311
  ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
@@ -1261,10 +1314,10 @@ export function StandaloneLayout(props) {
1261
1314
  }, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1262
1315
  const buildBillingApiUrl = useCallback((path) => {
1263
1316
  const normalizedPath = String(path || '').trim();
1264
- if (!billingApiBaseUrl)
1317
+ if (!accountCapabilityApiBaseUrl)
1265
1318
  return normalizedPath;
1266
- return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
1267
- }, [billingApiBaseUrl]);
1319
+ return `${accountCapabilityApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
1320
+ }, [accountCapabilityApiBaseUrl]);
1268
1321
  const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
1269
1322
  String(props.config?.cloudBaseUrl || ''),
1270
1323
  String(props.config?.cloudAuthBaseUrl || ''),
@@ -1282,9 +1335,141 @@ export function StandaloneLayout(props) {
1282
1335
  if (!showEditProfileModal)
1283
1336
  return;
1284
1337
  setProfileDisplayNameDraft(authDisplayName);
1338
+ setProfileAvatarPreviewUrl(authAvatarUrl);
1339
+ setProfileAvatarDraftId(null);
1340
+ setProfileSaveError(null);
1341
+ setProfileSaveNotice(null);
1342
+ }, [authAvatarUrl, authDisplayName, showEditProfileModal]);
1343
+ const discardProfileAvatarDraft = useCallback(async (draftId) => {
1344
+ const normalizedDraftId = String(draftId || '').trim();
1345
+ if (!normalizedDraftId || !cloudAuthConfigured)
1346
+ return;
1347
+ try {
1348
+ const baseUrl = runtimeMode === 'local'
1349
+ ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1350
+ : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1351
+ await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/discard', {
1352
+ method: 'POST',
1353
+ headers: { 'Content-Type': 'application/json' },
1354
+ credentials: 'include',
1355
+ body: JSON.stringify({ draftId: normalizedDraftId })
1356
+ }, baseUrl);
1357
+ }
1358
+ catch {
1359
+ // Best-effort cleanup only.
1360
+ }
1361
+ }, [cloudAuthConfigured, props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1362
+ const closeEditProfileModal = useCallback(() => {
1363
+ const pendingDraftId = profileAvatarDraftId;
1364
+ setShowEditProfileModal(false);
1285
1365
  setProfileSaveError(null);
1286
1366
  setProfileSaveNotice(null);
1287
- }, [authDisplayName, showEditProfileModal]);
1367
+ setProfileAvatarDraftId(null);
1368
+ setProfileAvatarPreviewUrl(authAvatarUrl);
1369
+ if (pendingDraftId) {
1370
+ void discardProfileAvatarDraft(pendingDraftId);
1371
+ }
1372
+ }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1373
+ const handleProfileAvatarSelected = useCallback(async (file) => {
1374
+ if (!file || !cloudAuthConfigured)
1375
+ return;
1376
+ if (!file.type.startsWith('image/')) {
1377
+ setProfileSaveError('Profile photo must be an image file.');
1378
+ setProfileSaveNotice(null);
1379
+ return;
1380
+ }
1381
+ if (file.size > PROFILE_AVATAR_MAX_BYTES) {
1382
+ setProfileSaveError('Profile photo must be 5 MB or smaller.');
1383
+ setProfileSaveNotice(null);
1384
+ return;
1385
+ }
1386
+ setProfileAvatarBusy(true);
1387
+ setProfileSaveError(null);
1388
+ setProfileSaveNotice(null);
1389
+ try {
1390
+ const baseUrl = runtimeMode === 'local'
1391
+ ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
1392
+ : (props.config?.apiBaseUrl || props.config?.cloudAuthBaseUrl || '');
1393
+ const initRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/init', {
1394
+ method: 'POST',
1395
+ headers: { 'Content-Type': 'application/json' },
1396
+ credentials: 'include',
1397
+ body: JSON.stringify({
1398
+ originalName: file.name,
1399
+ mimeType: file.type,
1400
+ size: file.size
1401
+ })
1402
+ }, baseUrl);
1403
+ const initData = await initRes.json().catch(() => ({}));
1404
+ if (!initRes.ok || !initData?.success || typeof initData?.uploadUrl !== 'string' || typeof initData?.relativePath !== 'string') {
1405
+ throw new Error(initData?.error || 'Failed to start avatar upload.');
1406
+ }
1407
+ const uploadRes = await fetch(initData.uploadUrl, {
1408
+ method: String(initData.method || 'PUT'),
1409
+ headers: initData.headers || { 'Content-Type': file.type || 'application/octet-stream' },
1410
+ body: file
1411
+ });
1412
+ if (!uploadRes.ok) {
1413
+ throw new Error('Failed to upload avatar.');
1414
+ }
1415
+ const finalizeRes = await fetchTaskforceApi('/api/taskforce/auth/profile/avatar/finalize', {
1416
+ method: 'POST',
1417
+ headers: { 'Content-Type': 'application/json' },
1418
+ credentials: 'include',
1419
+ body: JSON.stringify({
1420
+ draftId: initData.draftId,
1421
+ relativePath: initData.relativePath
1422
+ })
1423
+ }, baseUrl);
1424
+ const finalizeData = await finalizeRes.json().catch(() => ({}));
1425
+ if (!finalizeRes.ok || !finalizeData?.success || typeof finalizeData?.draftId !== 'string') {
1426
+ throw new Error(finalizeData?.error || 'Failed to finalize avatar upload.');
1427
+ }
1428
+ const previousDraftId = profileAvatarDraftId;
1429
+ setProfileAvatarDraftId(finalizeData.draftId);
1430
+ setProfileAvatarPreviewUrl(typeof finalizeData?.avatarUrl === 'string' ? finalizeData.avatarUrl : '');
1431
+ setProfileSaveNotice('Profile photo ready to save.');
1432
+ if (previousDraftId && previousDraftId !== finalizeData.draftId) {
1433
+ void discardProfileAvatarDraft(previousDraftId);
1434
+ }
1435
+ }
1436
+ catch (error) {
1437
+ setProfileSaveError(error instanceof Error ? error.message : 'Failed to upload profile photo.');
1438
+ }
1439
+ finally {
1440
+ setProfileAvatarBusy(false);
1441
+ if (profileAvatarInputRef.current) {
1442
+ profileAvatarInputRef.current.value = '';
1443
+ }
1444
+ }
1445
+ }, [
1446
+ cloudAuthConfigured,
1447
+ discardProfileAvatarDraft,
1448
+ profileAvatarDraftId,
1449
+ props.config?.apiBaseUrl,
1450
+ props.config?.cloudAuthBaseUrl,
1451
+ runtimeMode
1452
+ ]);
1453
+ const handleDiscardPendingProfileAvatar = useCallback(() => {
1454
+ if (!profileAvatarDraftId)
1455
+ return;
1456
+ const draftId = profileAvatarDraftId;
1457
+ setProfileAvatarDraftId(null);
1458
+ setProfileAvatarPreviewUrl(authAvatarUrl);
1459
+ setProfileSaveNotice(null);
1460
+ setProfileSaveError(null);
1461
+ void discardProfileAvatarDraft(draftId);
1462
+ }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1463
+ const handleRemoveProfileAvatar = useCallback(() => {
1464
+ const pendingDraftId = profileAvatarDraftId;
1465
+ setProfileAvatarDraftId(null);
1466
+ setProfileAvatarPreviewUrl('');
1467
+ setProfileSaveNotice(authAvatarUrl ? 'Profile photo will be removed when you save.' : null);
1468
+ setProfileSaveError(null);
1469
+ if (pendingDraftId) {
1470
+ void discardProfileAvatarDraft(pendingDraftId);
1471
+ }
1472
+ }, [authAvatarUrl, discardProfileAvatarDraft, profileAvatarDraftId]);
1288
1473
  const handleSaveProfile = useCallback(async () => {
1289
1474
  const trimmedDisplayName = profileDisplayNameDraft.trim();
1290
1475
  if (!trimmedDisplayName) {
@@ -1295,53 +1480,111 @@ export function StandaloneLayout(props) {
1295
1480
  setProfileSaveBusy(true);
1296
1481
  setProfileSaveError(null);
1297
1482
  setProfileSaveNotice(null);
1298
- const result = await updateCurrentUserProfile({ displayName: trimmedDisplayName });
1483
+ const result = await updateCurrentUserProfile({
1484
+ displayName: trimmedDisplayName,
1485
+ avatarDraftId: profileAvatarDraftId,
1486
+ clearAvatar: !profileAvatarDraftId && !profileAvatarPreviewUrl && Boolean(authAvatarUrl)
1487
+ });
1299
1488
  if (!result.success) {
1300
1489
  setProfileSaveError(result.error || 'Failed to update profile.');
1301
1490
  setProfileSaveBusy(false);
1302
1491
  return;
1303
1492
  }
1304
1493
  setProfileSaveNotice('Profile updated.');
1494
+ setProfileAvatarDraftId(null);
1495
+ setProfileAvatarPreviewUrl('');
1305
1496
  setProfileSaveBusy(false);
1306
1497
  setShowEditProfileModal(false);
1307
- }, [profileDisplayNameDraft, updateCurrentUserProfile]);
1498
+ }, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
1308
1499
  const teamAdminHeaders = useCallback(() => {
1309
1500
  const headers = {};
1310
1501
  const workspaceId = String(teamManagementWorkspaceId || '').trim();
1311
1502
  if (workspaceId)
1312
1503
  headers['x-taskforce-workspace-id'] = workspaceId;
1504
+ if (currentWorkspaceRole === 'owner'
1505
+ || currentWorkspaceRole === 'admin'
1506
+ || currentWorkspaceRole === 'member'
1507
+ || currentWorkspaceRole === 'read-only') {
1508
+ headers['x-taskforce-workspace-role'] = currentWorkspaceRole;
1509
+ }
1313
1510
  return headers;
1314
- }, [teamManagementWorkspaceId]);
1511
+ }, [currentWorkspaceRole, teamManagementWorkspaceId]);
1512
+ const startAccountSurfaceTiming = useCallback((surface) => {
1513
+ const expectsBilling = isAuthenticated;
1514
+ const expectsTeamManagement = canAccessCloudTeamManagement;
1515
+ accountSurfaceOpenStartedAtRef.current = getTimingNow();
1516
+ accountSurfacePendingRef.current = {
1517
+ billing: expectsBilling,
1518
+ teamManagement: expectsTeamManagement
1519
+ };
1520
+ logBootstrapDebug('account_surface_opened', {
1521
+ surface,
1522
+ expectsBilling,
1523
+ expectsTeamManagement
1524
+ });
1525
+ if (!expectsBilling && !expectsTeamManagement) {
1526
+ logBootstrapDebug('account_surface_ready', {
1527
+ surface,
1528
+ durationMs: 0,
1529
+ teamManagementVisible: false,
1530
+ billingPlanId: null
1531
+ });
1532
+ accountSurfaceOpenStartedAtRef.current = null;
1533
+ }
1534
+ }, [canAccessCloudTeamManagement, isAuthenticated]);
1535
+ const markAccountSurfaceLoadSettled = useCallback((kind) => {
1536
+ const pending = accountSurfacePendingRef.current;
1537
+ pending[kind] = false;
1538
+ if (pending.billing || pending.teamManagement)
1539
+ return;
1540
+ const startedAt = accountSurfaceOpenStartedAtRef.current;
1541
+ if (startedAt === null)
1542
+ return;
1543
+ logBootstrapDebug('account_surface_ready', {
1544
+ surface: showAccountHub ? 'account_hub' : (showAccountMenu ? 'account_menu' : 'closed'),
1545
+ durationMs: Math.round(getTimingNow() - startedAt),
1546
+ teamManagementVisible: canOpenTeamManagement,
1547
+ billingPlanId: String(billingStatus?.planId || '').trim() || null
1548
+ });
1549
+ accountSurfaceOpenStartedAtRef.current = null;
1550
+ }, [billingStatus?.planId, canOpenTeamManagement, showAccountHub, showAccountMenu]);
1315
1551
  const loadBillingStatus = useCallback(async () => {
1552
+ const billingStatusPath = '/api/taskforce/billing/status';
1553
+ const billingStatusUrl = buildBillingApiUrl(billingStatusPath);
1316
1554
  if (!isAuthenticated) {
1317
1555
  setBillingStatus(null);
1556
+ setBillingError(null);
1557
+ invalidateBillingStatusCache(billingStatusUrl);
1558
+ markAccountSurfaceLoadSettled('billing');
1318
1559
  return null;
1319
1560
  }
1561
+ const startedAt = getTimingNow();
1320
1562
  setBillingLoading(true);
1321
1563
  setBillingError(null);
1322
1564
  try {
1323
- const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/status'), {
1324
- method: 'GET',
1325
- credentials: 'include'
1326
- });
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
- }
1565
+ const payload = await loadBillingStatusCached(billingStatusUrl);
1333
1566
  setBillingStatus(payload);
1567
+ logBootstrapDebug('account_billing_status_resolved', {
1568
+ durationMs: Math.round(getTimingNow() - startedAt),
1569
+ planId: String(payload?.planId || '').trim() || null,
1570
+ gate: String(payload?.gate || '').trim() || null
1571
+ });
1334
1572
  return payload;
1335
1573
  }
1336
- catch {
1574
+ catch (error) {
1337
1575
  setBillingStatus(null);
1338
- setBillingError('Failed to load billing status.');
1576
+ setBillingError(error instanceof Error ? error.message : 'Failed to load billing status.');
1577
+ logBootstrapDebug('account_billing_status_failed', {
1578
+ durationMs: Math.round(getTimingNow() - startedAt),
1579
+ error: error instanceof Error ? error.message : 'Failed to load billing status.'
1580
+ });
1339
1581
  return null;
1340
1582
  }
1341
1583
  finally {
1342
1584
  setBillingLoading(false);
1585
+ markAccountSurfaceLoadSettled('billing');
1343
1586
  }
1344
- }, [buildBillingApiUrl, isAuthenticated]);
1587
+ }, [buildBillingApiUrl, isAuthenticated, markAccountSurfaceLoadSettled]);
1345
1588
  const handleStartCheckout = useCallback(async () => {
1346
1589
  const params = new URLSearchParams();
1347
1590
  params.set('screen', 'plans');
@@ -1401,6 +1644,7 @@ export function StandaloneLayout(props) {
1401
1644
  return;
1402
1645
  }
1403
1646
  setBillingNotice('Subscription updated.');
1647
+ invalidateBillingStatusCache(buildBillingApiUrl('/api/taskforce/billing/status'));
1404
1648
  await loadBillingStatus();
1405
1649
  }
1406
1650
  catch {
@@ -1410,45 +1654,88 @@ export function StandaloneLayout(props) {
1410
1654
  setBillingActionBusy(false);
1411
1655
  }
1412
1656
  }, [billingIntervalChoice, billingStatus?.planVersionId, buildBillingApiUrl, loadBillingStatus]);
1413
- const loadTeamPlanMode = useCallback(async () => {
1414
- if (!canManageTeamWorkspace) {
1657
+ const loadTeamManagementAccess = useCallback(async () => {
1658
+ if (!canAccessCloudTeamManagement) {
1659
+ setTeamWorkspaceRole(null);
1415
1660
  setTeamPlanMode('unknown');
1416
- return 'unknown';
1417
- }
1661
+ setTeamManagementAccessLoading(false);
1662
+ setTeamManagementAccessError(null);
1663
+ markAccountSurfaceLoadSettled('teamManagement');
1664
+ return { role: null, teamPlanMode: 'unknown', allowed: false };
1665
+ }
1666
+ const startedAt = getTimingNow();
1667
+ setTeamManagementAccessLoading(true);
1668
+ setTeamManagementAccessError(null);
1418
1669
  try {
1419
- const res = await fetch('/api/taskforce/account/entitlements', {
1670
+ const res = await fetchTaskforceApi('/api/taskforce/account/team-management-access', {
1420
1671
  method: 'GET',
1421
1672
  credentials: 'include',
1422
1673
  headers: teamAdminHeaders()
1423
- });
1674
+ }, accountCapabilityApiBaseUrl);
1424
1675
  const payload = await res.json().catch(() => ({}));
1425
1676
  if (!res.ok) {
1677
+ setTeamWorkspaceRole(null);
1426
1678
  setTeamPlanMode('unknown');
1427
- return 'unknown';
1679
+ setTeamManagementAccessError(String(payload?.error || 'Failed to load team management access.'));
1680
+ logBootstrapDebug('team_management_access_failed', {
1681
+ durationMs: Math.round(getTimingNow() - startedAt),
1682
+ status: res.status
1683
+ });
1684
+ return { role: null, teamPlanMode: 'unknown', allowed: false };
1428
1685
  }
1429
- const nextMode = resolveTeamPlanModeFromEntitlements(payload);
1686
+ const nextRole = payload?.role === 'owner' || payload?.role === 'admin' || payload?.role === 'member' || payload?.role === 'read-only'
1687
+ ? payload.role
1688
+ : null;
1689
+ const nextMode = payload?.teamPlanMode === 'team' || payload?.teamPlanMode === 'personal'
1690
+ ? payload.teamPlanMode
1691
+ : 'unknown';
1692
+ const allowed = payload?.allowed === true;
1693
+ setTeamWorkspaceRole(nextRole);
1430
1694
  setTeamPlanMode(nextMode);
1431
- return nextMode;
1695
+ setTeamManagementAccessError(null);
1696
+ logBootstrapDebug('team_management_access_loaded', {
1697
+ durationMs: Math.round(getTimingNow() - startedAt),
1698
+ role: nextRole,
1699
+ teamPlanMode: nextMode,
1700
+ allowed
1701
+ });
1702
+ return { role: nextRole, teamPlanMode: nextMode, allowed };
1432
1703
  }
1433
- catch {
1704
+ catch (error) {
1705
+ setTeamWorkspaceRole(null);
1434
1706
  setTeamPlanMode('unknown');
1435
- return 'unknown';
1707
+ setTeamManagementAccessError(error instanceof Error ? error.message : 'Failed to load team management access.');
1708
+ logBootstrapDebug('team_management_access_failed', {
1709
+ durationMs: Math.round(getTimingNow() - startedAt),
1710
+ error: error instanceof Error ? error.message : 'Failed to load team management access.'
1711
+ });
1712
+ return { role: null, teamPlanMode: 'unknown', allowed: false };
1436
1713
  }
1437
- }, [canManageTeamWorkspace, teamAdminHeaders]);
1714
+ finally {
1715
+ setTeamManagementAccessLoading(false);
1716
+ markAccountSurfaceLoadSettled('teamManagement');
1717
+ }
1718
+ }, [accountCapabilityApiBaseUrl, canAccessCloudTeamManagement, markAccountSurfaceLoadSettled, teamAdminHeaders]);
1438
1719
  useEffect(() => {
1439
1720
  if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
1440
1721
  return;
1441
- void loadTeamPlanMode();
1442
- }, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
1722
+ void loadTeamManagementAccess();
1723
+ }, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamManagementAccess]);
1443
1724
  useEffect(() => {
1444
1725
  if (!isAuthenticated || !authSessionResolved)
1445
1726
  return;
1446
- if (!canManageTeamWorkspace) {
1727
+ if (!canAccessCloudTeamManagement) {
1728
+ setTeamWorkspaceRole(null);
1447
1729
  setTeamPlanMode('unknown');
1448
1730
  return;
1449
1731
  }
1450
- void loadTeamPlanMode();
1451
- }, [authSessionResolved, canManageTeamWorkspace, isAuthenticated, loadTeamPlanMode]);
1732
+ void loadTeamManagementAccess();
1733
+ }, [authSessionResolved, canAccessCloudTeamManagement, isAuthenticated, loadTeamManagementAccess]);
1734
+ useEffect(() => {
1735
+ if (!isAuthenticated || !authSessionResolved)
1736
+ return;
1737
+ void loadBillingStatus();
1738
+ }, [authSessionResolved, isAuthenticated, loadBillingStatus]);
1452
1739
  useEffect(() => {
1453
1740
  if (!showAccountHub && !showAccountMenu)
1454
1741
  return;
@@ -1582,12 +1869,12 @@ export function StandaloneLayout(props) {
1582
1869
  setShowAccountMenu(false);
1583
1870
  setShowTeamManagementModal(true);
1584
1871
  setTeamManagementTab('members');
1585
- const resolvedMode = await loadTeamPlanMode();
1586
- if (resolvedMode !== 'team')
1872
+ const access = await loadTeamManagementAccess();
1873
+ if (!access.allowed)
1587
1874
  return;
1588
1875
  await loadTeamUsers();
1589
1876
  await loadTeamAuditPage(0);
1590
- }, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
1877
+ }, [loadTeamAuditPage, loadTeamManagementAccess, loadTeamUsers]);
1591
1878
  const executeTeamUserAction = useCallback(async (userId, fn) => {
1592
1879
  setTeamMgmtError(null);
1593
1880
  setTeamActionBusyUserId(userId);
@@ -1891,12 +2178,17 @@ export function StandaloneLayout(props) {
1891
2178
  const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
1892
2179
  const details = event.details && typeof event.details === 'object' ? event.details : null;
1893
2180
  const pathValue = String(details?.path || '').trim();
2181
+ const referenceLabelValue = String(details?.referenceLabel || '').trim();
2182
+ const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
1894
2183
  const existingRefValue = Number(details?.existingReferenceNumber);
1895
2184
  const incomingRefValue = Number(details?.incomingReferenceNumber);
1896
- const pathLabel = pathValue || `Mismatch ${index + 1}`;
1897
- const refsLabel = (Number.isFinite(existingRefValue) || Number.isFinite(incomingRefValue))
2185
+ const pathLabel = pathValue || taskTitleValue || referenceLabelValue || `Mismatch ${index + 1}`;
2186
+ const refsLabel = (Number.isFinite(existingRefValue)
2187
+ || Number.isFinite(incomingRefValue))
1898
2188
  ? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
1899
- : null;
2189
+ : referenceLabelValue
2190
+ ? `Both claimed ${referenceLabelValue}`
2191
+ : null;
1900
2192
  return {
1901
2193
  key: getSyncEventStableKey(event, index),
1902
2194
  pathLabel,
@@ -2255,20 +2547,32 @@ export function StandaloneLayout(props) {
2255
2547
  // References: isAuthenticated, authIdentityLabel, billingStatus, runtimeLabel,
2256
2548
  // connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
2257
2549
  // 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
2550
+ const accountMenuControl = (_jsxs("div", { className: accountStyles.accountMenuWrap, ref: accountMenuRef, children: [_jsx("button", { className: `tf-control-icon ${accountStyles.avatarBtn}`, onClick: () => {
2551
+ setShowAccountMenu((prev) => {
2552
+ const next = !prev;
2553
+ if (next) {
2554
+ startAccountSurfaceTiming('account_menu');
2555
+ }
2556
+ else {
2557
+ accountSurfaceOpenStartedAtRef.current = null;
2558
+ accountSurfacePendingRef.current = { billing: false, teamManagement: false };
2559
+ }
2560
+ return next;
2561
+ });
2562
+ }, 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 && accountAvatarUrl ? (_jsx("img", { src: accountAvatarUrl, alt: "", className: accountStyles.avatarImage })) : (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
2259
2563
  ? 'Checking sign-in status...'
2260
2564
  : isAuthenticated
2261
2565
  ? (_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: () => {
2566
+ ? (_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
2567
  setShowAccountMenu(false);
2264
2568
  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 })] })] }) }))
2569
+ }, 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
2570
  : 'Signed in' }))
2267
2571
  : '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
2572
  setWorkspaceActionError('');
2269
2573
  setShowCreateWorkspaceConfirm(true);
2270
2574
  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: () => {
2575
+ }, 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
2576
  setShowAccountMenu(false);
2273
2577
  setShowEditProfileModal(true);
2274
2578
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
@@ -2276,6 +2580,7 @@ export function StandaloneLayout(props) {
2276
2580
  openPlansScreen();
2277
2581
  }, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2278
2582
  setShowAccountMenu(false);
2583
+ startAccountSurfaceTiming('account_hub');
2279
2584
  setShowAccountHub(true);
2280
2585
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2281
2586
  setShowAccountMenu(false);
@@ -2908,7 +3213,7 @@ export function StandaloneLayout(props) {
2908
3213
  const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
2909
3214
  ? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
2910
3215
  : 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: {
3216
+ 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: "Open tasks assigned to you", children: ["Mine ", assignedTaskCount] }), _jsxs("span", { className: styles.taskCountBadge, title: "Your open tasks with overdue due dates", children: ["Overdue ", assignedOverdueTaskCount] })] })), shouldShowSyncStatus && (_jsx("button", { className: "tf-control-icon", onClick: () => setShowSyncStatusModal(true), title: `Sync manager: ${syncStatusMeta.label} | Last success: ${formattedLastSyncTime}`, style: {
2912
3217
  marginLeft: '6px',
2913
3218
  height: '24px',
2914
3219
  width: '24px',
@@ -3041,114 +3346,71 @@ export function StandaloneLayout(props) {
3041
3346
  return;
3042
3347
  }
3043
3348
  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: () => {
3349
+ }, 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: () => {
3350
+ setShowHelpModal(false);
3351
+ setActiveTab('settings');
3352
+ setSettingsSection('general');
3353
+ } }), _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, billingStatus: billingStatus, currentWorkspaceId: currentWorkspaceId, canOpenTeamManagement: canOpenTeamManagement, canManageWorkspaceSync: canManageWorkspaceSync, workspaceCloudSyncEnabled: workspaceCloudSyncEnabled, syncStatusLabel: syncStatusMeta.label, workspaceSyncError: workspaceSyncError, syncControlBusy: syncControlBusy, onClose: () => setShowAccountHub(false), onOpenWorkspaceAudit: () => {
3354
+ setShowAccountHub(false);
3355
+ void openTeamManagementModal();
3356
+ setTeamManagementTab('audit');
3357
+ }, onBillingIntervalChange: setBillingIntervalChoice, onRefreshBilling: () => { void loadBillingStatus(); }, onUpdateInterval: () => { void handleSwitchSubscriptionInterval(); }, onManageBilling: () => { void handleOpenBillingPortal(); }, onStartCheckout: () => { void handleStartCheckout(); }, onToggleWorkspaceSync: (enabled) => { void handleWorkspaceSyncToggle(enabled); }, onOpenHelp: () => {
3358
+ setShowAccountHub(false);
3359
+ setShowHelpModal(true);
3360
+ } }), _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: () => {
3361
+ setTeamManagementTab('members');
3362
+ void loadTeamUsers();
3363
+ }, onOpenInvitesTab: () => setTeamManagementTab('invites'), onOpenAuditTab: () => {
3364
+ setTeamManagementTab('audit');
3365
+ void loadTeamAuditPage(teamAuditPage);
3366
+ }, onMemberRoleChange: (userId, role) => {
3367
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/role`, {
3368
+ method: 'PATCH',
3369
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3370
+ credentials: 'include',
3371
+ body: JSON.stringify({
3372
+ workspaceId: teamManagementWorkspaceId,
3373
+ role
3374
+ })
3375
+ }));
3376
+ }, onMemberPermissionChange: (userId, mode) => {
3377
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/permission-mode`, {
3378
+ method: 'PATCH',
3379
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3380
+ credentials: 'include',
3381
+ body: JSON.stringify({
3382
+ workspaceId: teamManagementWorkspaceId,
3383
+ mode
3384
+ })
3385
+ }));
3386
+ }, onToggleMemberDisabled: (userId, disabled) => {
3387
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/disable`, {
3388
+ method: 'PATCH',
3389
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3390
+ credentials: 'include',
3391
+ body: JSON.stringify({
3392
+ workspaceId: teamManagementWorkspaceId,
3393
+ disabled
3394
+ })
3395
+ }));
3396
+ }, onRevokeInvite: (userId) => {
3397
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}/invite/revoke`, {
3398
+ method: 'POST',
3399
+ headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3400
+ credentials: 'include',
3401
+ body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
3402
+ }));
3403
+ }, onRemoveMember: (userId) => {
3404
+ void executeTeamUserAction(userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
3405
+ method: 'DELETE',
3406
+ headers: teamAdminHeaders(),
3407
+ credentials: 'include'
3408
+ }));
3409
+ }, 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) => {
3410
+ void handleWorkspaceSyncToggle(enabled);
3411
+ }, onRepairSync: handleQueueOrRunRepairSync, onCopyReport: () => { void handleCopySyncDetails(); }, onOpenLogin: () => {
3412
+ openSharedLoginRoute('login');
3413
+ }, 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
3414
  const parts = currentBrowsePath.split('/').filter(Boolean);
3153
3415
  parts.pop();
3154
3416
  fetchFolders(parts.length ? parts.join('/') + '/' : '');