@taskforcehq/taskforce 0.3.300 → 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 (153) hide show
  1. package/README.md +19 -0
  2. package/dist/Taskforce.module.css +26 -2
  3. package/dist/TaskforceCore.filter-persistence.test.js +1 -1
  4. package/dist/TaskforceCore.js +13 -24
  5. package/dist/TaskforceCore.test.js +167 -152
  6. package/dist/components/features/TaskSettings.js +14 -82
  7. package/dist/components/features/TaskSettings.test.js +33 -70
  8. package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
  9. package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
  10. package/dist/components/task/TaskActionHeader.js +6 -3
  11. package/dist/components/task/TaskCard.js +6 -3
  12. package/dist/components/task/TaskForm.js +2 -2
  13. package/dist/components/task/TaskForm.test.js +41 -2
  14. package/dist/components/ui/ReferenceBadgeButton.js +1 -1
  15. package/dist/components/views/PlansPage.js +9 -8
  16. package/dist/components/views/StandaloneLayout.js +490 -207
  17. package/dist/components/views/panels/FilterToolbar.d.ts +2 -0
  18. package/dist/components/views/panels/FilterToolbarControls.d.ts +1 -0
  19. package/dist/components/views/panels/FilterToolbarControls.js +1 -1
  20. package/dist/components/views/panels/PlanningDrawer.js +19 -2
  21. package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
  22. package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
  23. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
  24. package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
  25. package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
  26. package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
  27. package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
  28. package/dist/components/views/standalone/modals/HelpModal.js +8 -0
  29. package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
  30. package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
  31. package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
  32. package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
  33. package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
  34. package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
  35. package/dist/components/views/standalone/modals/types.d.ts +2 -0
  36. package/dist/components/views/standalone/modals/types.js +1 -0
  37. package/dist/components/views/teamManagementAccess.d.ts +1 -1
  38. package/dist/components/views/teamManagementAccess.js +2 -2
  39. package/dist/components/views/teamManagementAccess.test.js +3 -3
  40. package/dist/config/deployment.js +2 -2
  41. package/dist/config/deployment.test.js +5 -3
  42. package/dist/core/AiProfileService.js +7 -2
  43. package/dist/core/AiProfiles.test.js +19 -0
  44. package/dist/core/SyncReconciliationService.d.ts +2 -0
  45. package/dist/core/SyncReconciliationService.js +3 -0
  46. package/dist/core/TaskAuditTimeline.test.js +40 -0
  47. package/dist/core/Taskforce.d.ts +49 -0
  48. package/dist/core/Taskforce.ids.test.js +133 -0
  49. package/dist/core/Taskforce.js +241 -78
  50. package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
  51. package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
  52. package/dist/core/taskReferencePolicy.d.ts +28 -0
  53. package/dist/core/taskReferencePolicy.js +189 -0
  54. package/dist/core/types.d.ts +2 -0
  55. package/dist/hooks/tasks/useTaskFormActions.js +3 -0
  56. package/dist/hooks/useTaskData.js +16 -11
  57. package/dist/hooks/useTaskData.test.js +127 -0
  58. package/dist/hooks/useTaskforce.d.ts +5 -0
  59. package/dist/hooks/useTaskforce.js +63 -15
  60. package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
  61. package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
  62. package/dist/mcp/aiProfileBootstrap.js +80 -0
  63. package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
  64. package/dist/mcp/aiProfileBootstrap.test.js +68 -0
  65. package/dist/mcp/clientIntegrations.d.ts +2 -8
  66. package/dist/mcp/clientIntegrations.js +21 -40
  67. package/dist/mcp/clientIntegrations.test.js +28 -2
  68. package/dist/mcp/clientProfileDefaults.d.ts +12 -0
  69. package/dist/mcp/clientProfileDefaults.js +37 -0
  70. package/dist/mcp/runtime.d.ts +10 -7
  71. package/dist/mcp/runtime.js +45 -23
  72. package/dist/mcp/runtime.test.js +56 -3
  73. package/dist/migrations/taskSchemaMigrations.js +53 -1
  74. package/dist/qaOpen.test.d.ts +1 -0
  75. package/dist/qaOpen.test.js +56 -0
  76. package/dist/server/index.cookieProxy.test.js +1 -0
  77. package/dist/server/index.d.ts +1 -0
  78. package/dist/server/index.js +15 -1
  79. package/dist/server/index.test.js +14 -1
  80. package/dist/server/routes/admin.js +25 -22
  81. package/dist/server/routes/auth.d.ts +5 -0
  82. package/dist/server/routes/auth.js +350 -16
  83. package/dist/server/routes/billing.js +11 -1
  84. package/dist/server/routes/billing.test.js +32 -2
  85. package/dist/server/routes/documents.js +6 -1
  86. package/dist/server/routes/shared.d.ts +1 -1
  87. package/dist/server/routes/shared.js +11 -3
  88. package/dist/server/routes/sync.integration.test.js +130 -0
  89. package/dist/server/routes/sync.js +226 -1924
  90. package/dist/server/routes/tasks.js +18 -4
  91. package/dist/server/routes.js +26 -19
  92. package/dist/server/routes.test.js +352 -6
  93. package/dist/shared/runtimeContract.d.ts +1 -1
  94. package/dist/sync/collaborationSyncPayload.d.ts +17 -0
  95. package/dist/sync/collaborationSyncPayload.js +131 -0
  96. package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
  97. package/dist/sync/collaborationSyncPayload.test.js +104 -0
  98. package/dist/sync/collaborationSyncState.d.ts +15 -0
  99. package/dist/sync/collaborationSyncState.js +60 -0
  100. package/dist/sync/contentSyncPayload.d.ts +45 -0
  101. package/dist/sync/contentSyncPayload.js +104 -0
  102. package/dist/sync/contentSyncPayload.test.d.ts +1 -0
  103. package/dist/sync/contentSyncPayload.test.js +104 -0
  104. package/dist/sync/contentSyncState.d.ts +88 -0
  105. package/dist/sync/contentSyncState.js +743 -0
  106. package/dist/sync/planningSyncPayload.d.ts +9 -0
  107. package/dist/sync/planningSyncPayload.js +82 -0
  108. package/dist/sync/planningSyncPayload.test.d.ts +1 -0
  109. package/dist/sync/planningSyncPayload.test.js +68 -0
  110. package/dist/sync/syncApplyHandlers.d.ts +83 -0
  111. package/dist/sync/syncApplyHandlers.js +445 -0
  112. package/dist/sync/taskSyncPayload.d.ts +33 -0
  113. package/dist/sync/taskSyncPayload.js +126 -0
  114. package/dist/sync/taskSyncPayload.test.d.ts +1 -0
  115. package/dist/sync/taskSyncPayload.test.js +54 -0
  116. package/dist/sync/workspacePullFeed.d.ts +28 -0
  117. package/dist/sync/workspacePullFeed.js +301 -0
  118. package/dist/sync/workspacePullFeed.test.d.ts +1 -0
  119. package/dist/sync/workspacePullFeed.test.js +166 -0
  120. package/dist/sync/workspaceSyncModel.d.ts +4 -1
  121. package/dist/sync/workspaceSyncModel.js +9 -2
  122. package/dist/sync/workspaceSyncModel.test.js +29 -0
  123. package/dist/test/setup.js +1 -1
  124. package/dist/types.d.ts +1 -0
  125. package/dist/ui/assets/{AgentsModule-TVmCgizS.js → AgentsModule-JG5jc3he.js} +1 -1
  126. package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CAyhnPeR.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
  127. package/dist/ui/assets/{DocumentWorkspace-BOCw5e4D.js → DocumentWorkspace-iys3zfca.js} +2 -2
  128. package/dist/ui/assets/{InitiativesModule-CN0r02Oi.js → InitiativesModule-DJvLJI3A.js} +1 -1
  129. package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
  130. package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
  131. package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
  132. package/dist/ui/assets/{WorkflowsModule-CY9R2zPB.js → WorkflowsModule-BguHeQaV.js} +1 -1
  133. package/dist/ui/assets/index-MXeWoebC.css +1 -0
  134. package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
  135. package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
  136. package/dist/ui/index.html +3 -3
  137. package/dist/utils/billingStatusCache.d.ts +21 -0
  138. package/dist/utils/billingStatusCache.js +70 -0
  139. package/dist/utils/taskActivity.js +17 -4
  140. package/dist/utils/taskActivity.test.js +27 -0
  141. package/dist/utils/taskNormalization.js +10 -2
  142. package/dist/utils/taskReferences.d.ts +11 -1
  143. package/dist/utils/taskReferences.js +33 -1
  144. package/package.json +4 -1
  145. package/scripts/deploy-prod.sh +41 -0
  146. package/scripts/deploy-staging.sh +23 -0
  147. package/scripts/qa-open-lib.mjs +175 -0
  148. package/scripts/qa-open-lib.mjs.d.ts +49 -0
  149. package/scripts/qa-open.mjs +352 -0
  150. package/dist/ui/assets/PlansPage-FcQSNLZd.js +0 -1
  151. package/dist/ui/assets/TaskSettings-Cw5zYD1L.js +0 -8
  152. package/dist/ui/assets/index-Bzqy5QGm.js +0 -6
  153. package/dist/ui/assets/index-DxRiI3tY.css +0 -1
@@ -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,11 +922,32 @@ export function StandaloneLayout(props) {
890
922
  workstreamById,
891
923
  initiativeById,
892
924
  };
893
- }, [assigneeLabelByValue, hierarchyTaskById, hierarchyTasks, props.initiatives, props.workstreams, taskScope]);
894
- const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => ({ value: initiative.id, label: initiative.title })), [planningStructure.initiatives]);
925
+ }, [archivedScopeAllTasks, assigneeLabelByValue, deletedScopeAllTasks, hierarchyTaskById, props.initiatives, props.tasks, props.workstreams, taskScope]);
926
+ const initiativeFilterOptions = useMemo(() => planningStructure.initiatives.map((initiative) => {
927
+ const referenceLabel = formatInitiativeReference(initiative);
928
+ return {
929
+ value: initiative.id,
930
+ label: referenceLabel ? `${referenceLabel} - ${initiative.title}` : initiative.title,
931
+ selectedLabel: referenceLabel || initiative.title,
932
+ };
933
+ }), [planningStructure.initiatives]);
895
934
  const workstreamFilterOptions = useMemo(() => [
896
- ...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => ({ value: workstream.id, label: workstream.title }))),
897
- ...planningStructure.standaloneWorkstreams.map((workstream) => ({ value: workstream.id, label: workstream.title })),
935
+ ...planningStructure.initiatives.flatMap((initiative) => initiative.workstreams.map((workstream) => {
936
+ const referenceLabel = formatWorkstreamReference(workstream);
937
+ return {
938
+ value: workstream.id,
939
+ label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
940
+ selectedLabel: referenceLabel || workstream.title,
941
+ };
942
+ })),
943
+ ...planningStructure.standaloneWorkstreams.map((workstream) => {
944
+ const referenceLabel = formatWorkstreamReference(workstream);
945
+ return {
946
+ value: workstream.id,
947
+ label: referenceLabel ? `${referenceLabel} - ${workstream.title}` : workstream.title,
948
+ selectedLabel: referenceLabel || workstream.title,
949
+ };
950
+ }),
898
951
  ], [planningStructure.initiatives, planningStructure.standaloneWorkstreams]);
899
952
  const selectedPlanningTaskIds = useMemo(() => {
900
953
  if (selectedPlanningWorkstreamId) {
@@ -941,13 +994,6 @@ export function StandaloneLayout(props) {
941
994
  return exactInitiative;
942
995
  return resolveInitiativeReference(planningStructure.initiatives, planningDraftInitiativeId);
943
996
  }, [planningDraftInitiativeId, planningStructure.initiativeById, planningStructure.initiatives]);
944
- useEffect(() => {
945
- if (!planningDrawerOpen)
946
- return;
947
- setPlanningTreeCollapsed(false);
948
- setPlanningPrimaryCollapsed(false);
949
- setPlanningSecondaryCollapsed(false);
950
- }, [planningDrawerOpen]);
951
997
  useEffect(() => {
952
998
  if (selectedPlanningInitiativeId && !planningStructure.initiativeById.has(selectedPlanningInitiativeId)) {
953
999
  setSelectedPlanningInitiativeId('');
@@ -955,23 +1001,7 @@ export function StandaloneLayout(props) {
955
1001
  if (selectedPlanningWorkstreamId && !planningStructure.workstreamById.has(selectedPlanningWorkstreamId)) {
956
1002
  setSelectedPlanningWorkstreamId('');
957
1003
  }
958
- if (planningDrawerDetail?.type === 'initiative' && !planningStructure.initiativeById.has(planningDrawerDetail.id)) {
959
- setPlanningDrawerDetail(null);
960
- }
961
- if (planningDrawerDetail?.type === 'workstream' && !planningStructure.workstreamById.has(planningDrawerDetail.id)) {
962
- setPlanningDrawerDetail(null);
963
- }
964
- if (planningNestedWorkstreamDetailId && !planningStructure.workstreamById.has(planningNestedWorkstreamDetailId)) {
965
- setPlanningNestedWorkstreamDetailId(null);
966
- }
967
- if (planningSecondaryEditor?.mode === 'edit'
968
- && !planningStructure.workstreamById.has(planningSecondaryEditor.targetId)) {
969
- setPlanningSecondaryEditor(null);
970
- }
971
1004
  }, [
972
- planningDrawerDetail,
973
- planningNestedWorkstreamDetailId,
974
- planningSecondaryEditor,
975
1005
  planningStructure.initiativeById,
976
1006
  planningStructure.workstreamById,
977
1007
  selectedPlanningInitiativeId,
@@ -1038,6 +1068,29 @@ export function StandaloneLayout(props) {
1038
1068
  return !!task.dueDate && task.dueDate < todayDateOnly;
1039
1069
  }).length;
1040
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]);
1041
1094
  const expiredColumnCandidates = useMemo(() => {
1042
1095
  if (groupBy !== 'schedule')
1043
1096
  return [];
@@ -1147,7 +1200,10 @@ export function StandaloneLayout(props) {
1147
1200
  const [showCreateWorkspaceConfirm, setShowCreateWorkspaceConfirm] = useState(false);
1148
1201
  const [showTeamManagementModal, setShowTeamManagementModal] = useState(false);
1149
1202
  const [teamManagementTab, setTeamManagementTab] = useState('members');
1203
+ const [teamWorkspaceRole, setTeamWorkspaceRole] = useState(null);
1150
1204
  const [teamPlanMode, setTeamPlanMode] = useState('unknown');
1205
+ const [teamManagementAccessLoading, setTeamManagementAccessLoading] = useState(false);
1206
+ const [teamManagementAccessError, setTeamManagementAccessError] = useState(null);
1151
1207
  const [teamMgmtError, setTeamMgmtError] = useState(null);
1152
1208
  const [teamUsersLoading, setTeamUsersLoading] = useState(false);
1153
1209
  const [teamUsers, setTeamUsers] = useState([]);
@@ -1170,11 +1226,20 @@ export function StandaloneLayout(props) {
1170
1226
  const [billingNotice, setBillingNotice] = useState(null);
1171
1227
  const [billingIntervalChoice, setBillingIntervalChoice] = useState('month');
1172
1228
  const [profileDisplayNameDraft, setProfileDisplayNameDraft] = useState('');
1229
+ const [profileAvatarPreviewUrl, setProfileAvatarPreviewUrl] = useState('');
1230
+ const [profileAvatarDraftId, setProfileAvatarDraftId] = useState(null);
1231
+ const [profileAvatarBusy, setProfileAvatarBusy] = useState(false);
1173
1232
  const [profileSaveBusy, setProfileSaveBusy] = useState(false);
1174
1233
  const [profileSaveError, setProfileSaveError] = useState(null);
1175
1234
  const [profileSaveNotice, setProfileSaveNotice] = useState(null);
1176
1235
  const TEAM_AUDIT_PAGE_SIZE = 25;
1177
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
+ });
1178
1243
  // showSearch state removed - search always visible in actions area
1179
1244
  useEffect(() => {
1180
1245
  if (!showAccountMenu)
@@ -1218,20 +1283,29 @@ export function StandaloneLayout(props) {
1218
1283
  const fallbackId = String(currentWorkspaceId || '').trim();
1219
1284
  return fallbackId || 'Workspace';
1220
1285
  }, [availableWorkspaces, currentWorkspaceId, projectName, runtimeMode]);
1221
- const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, currentWorkspaceRole);
1222
- const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, currentWorkspaceRole, teamPlanMode);
1286
+ const canManageTeamWorkspace = computeCanManageTeamWorkspace(canAccessCloudTeamManagement, teamWorkspaceRole);
1287
+ const canOpenTeamManagement = computeCanOpenTeamManagement(canAccessCloudTeamManagement, teamWorkspaceRole, teamPlanMode);
1223
1288
  const authDisplayName = String(authUserDisplayName || '').trim();
1289
+ const authAvatarUrl = String(authUserAvatarUrl || '').trim();
1224
1290
  const authEmailLabel = String(authUserEmail || '').trim();
1225
1291
  const authIdentityLabel = authDisplayName || authEmailLabel;
1226
1292
  const hasAuthIdentity = authIdentityLabel.length > 0;
1227
1293
  const hasAuthEmail = authEmailLabel.length > 0;
1228
1294
  const accountBadgeLabel = (authIdentityLabel || authEmailLabel || 'U').trim();
1229
1295
  const accountBadgeInitial = accountBadgeLabel.charAt(0).toUpperCase() || 'U';
1296
+ const accountAvatarUrl = authAvatarUrl;
1297
+ const profileAvatarDisplayUrl = String(profileAvatarPreviewUrl || authAvatarUrl).trim();
1230
1298
  const subscriptionLabel = String(billingStatus?.planName || billingStatus?.planId || '').trim();
1231
1299
  const hasSubscription = subscriptionLabel.length > 0;
1232
1300
  const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();
1233
1301
  const runtimeLabel = runtimeMode === 'cloud' ? 'CLOUD' : 'LOCAL';
1234
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]);
1235
1309
  const billingApiBaseUrl = useMemo(() => {
1236
1310
  const preferredBase = runtimeMode === 'local'
1237
1311
  ? (props.config?.cloudAuthBaseUrl || props.config?.apiBaseUrl || '')
@@ -1240,10 +1314,10 @@ export function StandaloneLayout(props) {
1240
1314
  }, [props.config?.apiBaseUrl, props.config?.cloudAuthBaseUrl, runtimeMode]);
1241
1315
  const buildBillingApiUrl = useCallback((path) => {
1242
1316
  const normalizedPath = String(path || '').trim();
1243
- if (!billingApiBaseUrl)
1317
+ if (!accountCapabilityApiBaseUrl)
1244
1318
  return normalizedPath;
1245
- return `${billingApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
1246
- }, [billingApiBaseUrl]);
1319
+ return `${accountCapabilityApiBaseUrl}${normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`}`;
1320
+ }, [accountCapabilityApiBaseUrl]);
1247
1321
  const connectedEnvironmentLabel = useMemo(() => resolveConnectedEnvironmentLabel(props.config?.cloudEnvironment, [
1248
1322
  String(props.config?.cloudBaseUrl || ''),
1249
1323
  String(props.config?.cloudAuthBaseUrl || ''),
@@ -1261,9 +1335,141 @@ export function StandaloneLayout(props) {
1261
1335
  if (!showEditProfileModal)
1262
1336
  return;
1263
1337
  setProfileDisplayNameDraft(authDisplayName);
1338
+ setProfileAvatarPreviewUrl(authAvatarUrl);
1339
+ setProfileAvatarDraftId(null);
1264
1340
  setProfileSaveError(null);
1265
1341
  setProfileSaveNotice(null);
1266
- }, [authDisplayName, showEditProfileModal]);
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);
1365
+ setProfileSaveError(null);
1366
+ setProfileSaveNotice(null);
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]);
1267
1473
  const handleSaveProfile = useCallback(async () => {
1268
1474
  const trimmedDisplayName = profileDisplayNameDraft.trim();
1269
1475
  if (!trimmedDisplayName) {
@@ -1274,53 +1480,111 @@ export function StandaloneLayout(props) {
1274
1480
  setProfileSaveBusy(true);
1275
1481
  setProfileSaveError(null);
1276
1482
  setProfileSaveNotice(null);
1277
- const result = await updateCurrentUserProfile({ displayName: trimmedDisplayName });
1483
+ const result = await updateCurrentUserProfile({
1484
+ displayName: trimmedDisplayName,
1485
+ avatarDraftId: profileAvatarDraftId,
1486
+ clearAvatar: !profileAvatarDraftId && !profileAvatarPreviewUrl && Boolean(authAvatarUrl)
1487
+ });
1278
1488
  if (!result.success) {
1279
1489
  setProfileSaveError(result.error || 'Failed to update profile.');
1280
1490
  setProfileSaveBusy(false);
1281
1491
  return;
1282
1492
  }
1283
1493
  setProfileSaveNotice('Profile updated.');
1494
+ setProfileAvatarDraftId(null);
1495
+ setProfileAvatarPreviewUrl('');
1284
1496
  setProfileSaveBusy(false);
1285
1497
  setShowEditProfileModal(false);
1286
- }, [profileDisplayNameDraft, updateCurrentUserProfile]);
1498
+ }, [authAvatarUrl, profileAvatarDraftId, profileAvatarPreviewUrl, profileDisplayNameDraft, updateCurrentUserProfile]);
1287
1499
  const teamAdminHeaders = useCallback(() => {
1288
1500
  const headers = {};
1289
1501
  const workspaceId = String(teamManagementWorkspaceId || '').trim();
1290
1502
  if (workspaceId)
1291
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
+ }
1292
1510
  return headers;
1293
- }, [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]);
1294
1551
  const loadBillingStatus = useCallback(async () => {
1552
+ const billingStatusPath = '/api/taskforce/billing/status';
1553
+ const billingStatusUrl = buildBillingApiUrl(billingStatusPath);
1295
1554
  if (!isAuthenticated) {
1296
1555
  setBillingStatus(null);
1556
+ setBillingError(null);
1557
+ invalidateBillingStatusCache(billingStatusUrl);
1558
+ markAccountSurfaceLoadSettled('billing');
1297
1559
  return null;
1298
1560
  }
1561
+ const startedAt = getTimingNow();
1299
1562
  setBillingLoading(true);
1300
1563
  setBillingError(null);
1301
1564
  try {
1302
- const res = await fetch(buildBillingApiUrl('/api/taskforce/billing/status'), {
1303
- method: 'GET',
1304
- credentials: 'include'
1305
- });
1306
- const payload = await res.json().catch(() => ({}));
1307
- if (!res.ok) {
1308
- setBillingStatus(null);
1309
- setBillingError(String(payload?.error || 'Failed to load billing status.'));
1310
- return null;
1311
- }
1565
+ const payload = await loadBillingStatusCached(billingStatusUrl);
1312
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
+ });
1313
1572
  return payload;
1314
1573
  }
1315
- catch {
1574
+ catch (error) {
1316
1575
  setBillingStatus(null);
1317
- 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
+ });
1318
1581
  return null;
1319
1582
  }
1320
1583
  finally {
1321
1584
  setBillingLoading(false);
1585
+ markAccountSurfaceLoadSettled('billing');
1322
1586
  }
1323
- }, [buildBillingApiUrl, isAuthenticated]);
1587
+ }, [buildBillingApiUrl, isAuthenticated, markAccountSurfaceLoadSettled]);
1324
1588
  const handleStartCheckout = useCallback(async () => {
1325
1589
  const params = new URLSearchParams();
1326
1590
  params.set('screen', 'plans');
@@ -1380,6 +1644,7 @@ export function StandaloneLayout(props) {
1380
1644
  return;
1381
1645
  }
1382
1646
  setBillingNotice('Subscription updated.');
1647
+ invalidateBillingStatusCache(buildBillingApiUrl('/api/taskforce/billing/status'));
1383
1648
  await loadBillingStatus();
1384
1649
  }
1385
1650
  catch {
@@ -1389,45 +1654,88 @@ export function StandaloneLayout(props) {
1389
1654
  setBillingActionBusy(false);
1390
1655
  }
1391
1656
  }, [billingIntervalChoice, billingStatus?.planVersionId, buildBillingApiUrl, loadBillingStatus]);
1392
- const loadTeamPlanMode = useCallback(async () => {
1393
- if (!canManageTeamWorkspace) {
1657
+ const loadTeamManagementAccess = useCallback(async () => {
1658
+ if (!canAccessCloudTeamManagement) {
1659
+ setTeamWorkspaceRole(null);
1394
1660
  setTeamPlanMode('unknown');
1395
- return 'unknown';
1396
- }
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);
1397
1669
  try {
1398
- const res = await fetch('/api/taskforce/account/entitlements', {
1670
+ const res = await fetchTaskforceApi('/api/taskforce/account/team-management-access', {
1399
1671
  method: 'GET',
1400
1672
  credentials: 'include',
1401
1673
  headers: teamAdminHeaders()
1402
- });
1674
+ }, accountCapabilityApiBaseUrl);
1403
1675
  const payload = await res.json().catch(() => ({}));
1404
1676
  if (!res.ok) {
1677
+ setTeamWorkspaceRole(null);
1405
1678
  setTeamPlanMode('unknown');
1406
- 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 };
1407
1685
  }
1408
- 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);
1409
1694
  setTeamPlanMode(nextMode);
1410
- 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 };
1411
1703
  }
1412
- catch {
1704
+ catch (error) {
1705
+ setTeamWorkspaceRole(null);
1413
1706
  setTeamPlanMode('unknown');
1414
- 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 };
1415
1713
  }
1416
- }, [canManageTeamWorkspace, teamAdminHeaders]);
1714
+ finally {
1715
+ setTeamManagementAccessLoading(false);
1716
+ markAccountSurfaceLoadSettled('teamManagement');
1717
+ }
1718
+ }, [accountCapabilityApiBaseUrl, canAccessCloudTeamManagement, markAccountSurfaceLoadSettled, teamAdminHeaders]);
1417
1719
  useEffect(() => {
1418
1720
  if (!showAccountMenu && !showTeamManagementModal && !showAccountHub)
1419
1721
  return;
1420
- void loadTeamPlanMode();
1421
- }, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamPlanMode]);
1722
+ void loadTeamManagementAccess();
1723
+ }, [showAccountHub, showAccountMenu, showTeamManagementModal, loadTeamManagementAccess]);
1422
1724
  useEffect(() => {
1423
1725
  if (!isAuthenticated || !authSessionResolved)
1424
1726
  return;
1425
- if (!canManageTeamWorkspace) {
1727
+ if (!canAccessCloudTeamManagement) {
1728
+ setTeamWorkspaceRole(null);
1426
1729
  setTeamPlanMode('unknown');
1427
1730
  return;
1428
1731
  }
1429
- void loadTeamPlanMode();
1430
- }, [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]);
1431
1739
  useEffect(() => {
1432
1740
  if (!showAccountHub && !showAccountMenu)
1433
1741
  return;
@@ -1561,12 +1869,12 @@ export function StandaloneLayout(props) {
1561
1869
  setShowAccountMenu(false);
1562
1870
  setShowTeamManagementModal(true);
1563
1871
  setTeamManagementTab('members');
1564
- const resolvedMode = await loadTeamPlanMode();
1565
- if (resolvedMode !== 'team')
1872
+ const access = await loadTeamManagementAccess();
1873
+ if (!access.allowed)
1566
1874
  return;
1567
1875
  await loadTeamUsers();
1568
1876
  await loadTeamAuditPage(0);
1569
- }, [loadTeamAuditPage, loadTeamPlanMode, loadTeamUsers]);
1877
+ }, [loadTeamAuditPage, loadTeamManagementAccess, loadTeamUsers]);
1570
1878
  const executeTeamUserAction = useCallback(async (userId, fn) => {
1571
1879
  setTeamMgmtError(null);
1572
1880
  setTeamActionBusyUserId(userId);
@@ -1870,12 +2178,17 @@ export function StandaloneLayout(props) {
1870
2178
  const activeReferenceMismatchSummaries = useMemo(() => (activeReferenceMismatchEvents.map((event, index) => {
1871
2179
  const details = event.details && typeof event.details === 'object' ? event.details : null;
1872
2180
  const pathValue = String(details?.path || '').trim();
2181
+ const referenceLabelValue = String(details?.referenceLabel || '').trim();
2182
+ const taskTitleValue = String(details?.taskTitle || details?.existingTaskTitle || '').trim();
1873
2183
  const existingRefValue = Number(details?.existingReferenceNumber);
1874
2184
  const incomingRefValue = Number(details?.incomingReferenceNumber);
1875
- const pathLabel = pathValue || `Mismatch ${index + 1}`;
1876
- 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))
1877
2188
  ? `Existing ${Number.isFinite(existingRefValue) ? existingRefValue : '?'} vs incoming ${Number.isFinite(incomingRefValue) ? incomingRefValue : '?'}`
1878
- : null;
2189
+ : referenceLabelValue
2190
+ ? `Both claimed ${referenceLabelValue}`
2191
+ : null;
1879
2192
  return {
1880
2193
  key: getSyncEventStableKey(event, index),
1881
2194
  pathLabel,
@@ -2234,20 +2547,32 @@ export function StandaloneLayout(props) {
2234
2547
  // References: isAuthenticated, authIdentityLabel, billingStatus, runtimeLabel,
2235
2548
  // connectedEnvironmentLabel, canManageCloudWorkspaces, workspaceActionBusy,
2236
2549
  // setWorkspacePanelOpen, canOpenTeamManagement, openTeamManagementModal, and more.
2237
- 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
2238
2563
  ? 'Checking sign-in status...'
2239
2564
  : isAuthenticated
2240
2565
  ? (_jsx(_Fragment, { children: hasAuthIdentity
2241
- ? (_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: () => {
2242
2567
  setShowAccountMenu(false);
2243
2568
  openPlansScreen();
2244
- }, 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 })] })] }) }))
2245
2570
  : 'Signed in' }))
2246
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: () => {
2247
2572
  setWorkspaceActionError('');
2248
2573
  setShowCreateWorkspaceConfirm(true);
2249
2574
  setShowAccountMenu(false);
2250
- }, 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: () => {
2251
2576
  setShowAccountMenu(false);
2252
2577
  setShowEditProfileModal(true);
2253
2578
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Edit Profile"] })), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
@@ -2255,6 +2580,7 @@ export function StandaloneLayout(props) {
2255
2580
  openPlansScreen();
2256
2581
  }, role: "menuitem", children: [_jsx(Layers, { size: 15 }), "Plans"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2257
2582
  setShowAccountMenu(false);
2583
+ startAccountSurfaceTiming('account_hub');
2258
2584
  setShowAccountHub(true);
2259
2585
  }, role: "menuitem", children: [_jsx(User, { size: 15 }), "Account Hub"] }), _jsxs("button", { className: accountStyles.accountMenuItem, onClick: () => {
2260
2586
  setShowAccountMenu(false);
@@ -2887,7 +3213,7 @@ export function StandaloneLayout(props) {
2887
3213
  const workspacePrimaryAction = workspaceChromeDefinition.primaryAction
2888
3214
  ? primaryActionRegistry[workspaceChromeDefinition.primaryAction]
2889
3215
  : null;
2890
- 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: {
2891
3217
  marginLeft: '6px',
2892
3218
  height: '24px',
2893
3219
  width: '24px',
@@ -3020,114 +3346,71 @@ export function StandaloneLayout(props) {
3020
3346
  return;
3021
3347
  }
3022
3348
  void handleUnarchive(taskId);
3023
- }, 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: () => {
3024
- setShowHelpModal(false);
3025
- setActiveTab('settings');
3026
- setSettingsSection('general');
3027
- }, 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'
3028
- ? (authRequiredForApi
3029
- ? (isAuthenticated ? 'Signed in. API access is enabled.' : 'Cloud mode requires sign in before app usage.')
3030
- : 'Cloud runtime with optional authentication.')
3031
- : 'Local runtime allows guest usage without sign in.' }), _jsx("p", { className: managementStyles.sectionTextSpaced, children: isAuthenticated
3032
- ? (hasAuthIdentity
3033
- ? _jsxs(_Fragment, { children: ["Signed in as ", _jsx("span", { className: accountStyles.accountIdentityEmail, children: authIdentityLabel })] })
3034
- : 'Signed in')
3035
- : '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: () => {
3036
- setShowAccountHub(false);
3037
- void openTeamManagementModal();
3038
- setTeamManagementTab('audit');
3039
- }, 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
3040
- ? (workspaceCloudSyncEnabled
3041
- ? `Enabled · ${syncStatusMeta.label}`
3042
- : 'Disabled')
3043
- : '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: () => {
3044
- setShowAccountHub(false);
3045
- setShowHelpModal(true);
3046
- }, 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: () => {
3047
- setTeamManagementTab('members');
3048
- void loadTeamUsers();
3049
- }, 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: () => {
3050
- setTeamManagementTab('audit');
3051
- void loadTeamAuditPage(teamAuditPage);
3052
- }, 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) => {
3053
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/role`, {
3054
- method: 'PATCH',
3055
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3056
- credentials: 'include',
3057
- body: JSON.stringify({
3058
- workspaceId: teamManagementWorkspaceId,
3059
- role: event.target.value
3060
- })
3061
- }));
3062
- }, 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) => {
3063
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/permission-mode`, {
3064
- method: 'PATCH',
3065
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3066
- credentials: 'include',
3067
- body: JSON.stringify({
3068
- workspaceId: teamManagementWorkspaceId,
3069
- mode: event.target.value
3070
- })
3071
- }));
3072
- }, 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: () => {
3073
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/disable`, {
3074
- method: 'PATCH',
3075
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3076
- credentials: 'include',
3077
- body: JSON.stringify({
3078
- workspaceId: teamManagementWorkspaceId,
3079
- disabled: !member.disabled
3080
- })
3081
- }));
3082
- }, children: member.disabled ? 'Reactivate' : 'Deactivate' }), member.status === 'invited' && (_jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
3083
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}/invite/revoke`, {
3084
- method: 'POST',
3085
- headers: { 'Content-Type': 'application/json', ...teamAdminHeaders() },
3086
- credentials: 'include',
3087
- body: JSON.stringify({ workspaceId: teamManagementWorkspaceId })
3088
- }));
3089
- }, children: "Revoke Invite" })), _jsx("button", { className: styles.cancelBtn, disabled: teamActionBusyUserId === member.userId, onClick: () => {
3090
- void executeTeamUserAction(member.userId, async () => fetch(`/api/taskforce/admin/users/${encodeURIComponent(member.userId)}?workspaceId=${encodeURIComponent(teamManagementWorkspaceId)}`, {
3091
- method: 'DELETE',
3092
- headers: teamAdminHeaders(),
3093
- credentials: 'include'
3094
- }));
3095
- }, 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: () => {
3096
- setShowSyncStatusModal(false);
3097
- }, 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: {
3098
- borderColor: syncStatusMeta.border,
3099
- background: syncStatusMeta.background,
3100
- color: syncStatusMeta.color
3101
- }, children: syncStatusMeta.label }), _jsxs("span", { className: syncStatusStyles.syncStatusLabel, children: ["Workspace: ", _jsx("code", { children: currentWorkspaceLabel })] })] }) }), _jsx("label", { className: syncStatusStyles.syncToggle, children: _jsxs("span", { className: [
3102
- syncStatusStyles.syncToggleControl,
3103
- workspaceCloudSyncEnabled ? syncStatusStyles.syncToggleControlEnabled : syncStatusStyles.syncToggleControlDisabled,
3104
- syncControlBusy ? syncStatusStyles.syncToggleControlBusy : '',
3105
- !canManageWorkspaceSync ? syncStatusStyles.syncToggleControlBlocked : ''
3106
- ].filter(Boolean).join(' '), children: [_jsx("input", { className: syncStatusStyles.syncToggleInput, type: "checkbox", role: "switch", "aria-label": "Enable Sync", checked: workspaceCloudSyncEnabled, disabled: !canManageWorkspaceSync || syncControlBusy, onChange: (event) => {
3107
- void handleWorkspaceSyncToggle(event.target.checked);
3108
- } }), _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
3109
- ? `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.`
3110
- : '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: {
3111
- display: 'flex',
3112
- flexDirection: 'column',
3113
- gap: '2px',
3114
- padding: '8px 10px',
3115
- borderRadius: '8px',
3116
- background: 'rgba(15, 23, 42, 0.18)',
3117
- border: '1px solid rgba(250, 204, 21, 0.16)'
3118
- }, 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: [
3119
- syncStatusStyles.syncStatusEventItem,
3120
- row.tone === 'error' ? syncStatusStyles.syncStatusEventItemError : '',
3121
- row.tone === 'muted' ? syncStatusStyles.syncStatusEventItemMuted : ''
3122
- ].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
3123
- ? 'Repair is running now.'
3124
- : workspaceSyncRepairQueued
3125
- ? 'Repair is queued and will start when the current sync finishes.'
3126
- : workspaceSyncBusy
3127
- ? 'Queue a repair to start automatically when the current sync finishes.'
3128
- : '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: () => {
3129
- openSharedLoginRoute('login');
3130
- }, 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: () => {
3131
3414
  const parts = currentBrowsePath.split('/').filter(Boolean);
3132
3415
  parts.pop();
3133
3416
  fetchFolders(parts.length ? parts.join('/') + '/' : '');