@taskforcehq/taskforce 0.3.302 → 0.3.304
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.
- package/dist/Taskforce.module.css +113 -5
- package/dist/TaskforceCore.js +560 -208
- package/dist/TaskforceCore.test.js +1833 -91
- package/dist/annotatedAttachments/service.d.ts +1 -1
- package/dist/annotatedAttachments/types.d.ts +1 -1
- package/dist/components/features/AgentsModule.js +1 -1
- package/dist/components/features/AgentsModule.test.js +1 -1
- package/dist/components/features/TaskSettings.js +58 -38
- package/dist/components/features/TaskSettings.test.js +139 -40
- package/dist/components/task/TaskActionHeader.d.ts +2 -1
- package/dist/components/task/TaskActionHeader.js +3 -3
- package/dist/components/task/TaskActionHeader.test.js +8 -0
- package/dist/components/task/TaskCard.js +16 -1
- package/dist/components/task/TaskCard.test.js +31 -0
- package/dist/components/task/TaskForm.d.ts +2 -1
- package/dist/components/task/TaskForm.js +22 -4
- package/dist/components/task/TaskForm.test.js +46 -0
- package/dist/components/task/TaskKanban.d.ts +1 -1
- package/dist/components/task/TaskKanban.js +31 -160
- package/dist/components/task/TaskKanban.test.js +17 -0
- package/dist/components/task/TaskStatusActions.d.ts +6 -1
- package/dist/components/task/TaskStatusActions.js +8 -3
- package/dist/components/task/TaskStatusActions.test.js +9 -0
- package/dist/components/task/taskKanbanDropRules.d.ts +58 -0
- package/dist/components/task/taskKanbanDropRules.js +187 -0
- package/dist/components/task/taskKanbanDropRules.test.d.ts +1 -0
- package/dist/components/task/taskKanbanDropRules.test.js +114 -0
- package/dist/components/ui/Modal.d.ts +1 -1
- package/dist/components/ui/Modal.js +1 -0
- package/dist/components/views/AppShellHeader.js +5 -1
- package/dist/components/views/AppShellHeader.test.js +4 -0
- package/dist/components/views/PlanComparisonPage.d.ts +3 -2
- package/dist/components/views/PlanComparisonPage.js +4 -2
- package/dist/components/views/PlanComparisonPage.test.js +4 -0
- package/dist/components/views/PlansPage.d.ts +7 -1
- package/dist/components/views/PlansPage.js +139 -53
- package/dist/components/views/PlansPage.test.d.ts +1 -0
- package/dist/components/views/PlansPage.test.js +157 -0
- package/dist/components/views/StandaloneLayout.d.ts +1 -0
- package/dist/components/views/StandaloneLayout.js +216 -186
- package/dist/components/views/WidgetView.js +11 -2
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +8 -3
- package/dist/components/views/standalone/modals/AccountHubModal.js +16 -2
- package/dist/components/views/standalone/modals/AccountHubModal.test.d.ts +1 -0
- package/dist/components/views/standalone/modals/AccountHubModal.test.js +26 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +1 -1
- package/dist/core/DeletedTaskLifecycleService.d.ts +2 -1
- package/dist/core/EntitlementsPolicy.test.js +2 -2
- package/dist/core/GlobalSettingsService.js +78 -18
- package/dist/core/PlanEntitlementService.d.ts +31 -5
- package/dist/core/PlanEntitlementService.js +350 -222
- package/dist/core/SignupMonetizationSettings.test.js +208 -49
- package/dist/core/SystemAdmin.test.js +186 -96
- package/dist/core/TaskActivityService.d.ts +4 -1
- package/dist/core/TaskActivityService.js +45 -0
- package/dist/core/TaskAuditTimeline.test.js +52 -1
- package/dist/core/Taskforce.d.ts +65 -20
- package/dist/core/Taskforce.js +261 -51
- package/dist/core/Taskforce.mcpAuth.test.js +0 -2
- package/dist/core/UserProfileAvatarDrafts.test.js +12 -3
- package/dist/core/WorkspacePlanMode.test.js +65 -0
- package/dist/core/types.d.ts +11 -12
- package/dist/documentReviews/service.d.ts +1 -1
- package/dist/documentReviews/types.d.ts +1 -1
- package/dist/hooks/auth/useTaskforceAuthBootstrap.d.ts +48 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +219 -0
- package/dist/hooks/sync/auth.d.ts +37 -0
- package/dist/hooks/sync/auth.js +61 -0
- package/dist/hooks/sync/bootstrap.d.ts +34 -0
- package/dist/hooks/sync/bootstrap.js +52 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +36 -0
- package/dist/hooks/sync/orchestratorShared.js +209 -0
- package/dist/hooks/sync/orchestratorShared.test.d.ts +1 -0
- package/dist/hooks/sync/orchestratorShared.test.js +49 -0
- package/dist/hooks/sync/realtime.d.ts +25 -0
- package/dist/hooks/sync/realtime.js +60 -0
- package/dist/hooks/sync/recovery.d.ts +69 -0
- package/dist/hooks/sync/recovery.js +118 -0
- package/dist/hooks/sync/transfers.d.ts +122 -0
- package/dist/hooks/sync/transfers.js +431 -0
- package/dist/hooks/sync/useDataVersionRefresh.d.ts +16 -0
- package/dist/hooks/sync/useDataVersionRefresh.js +73 -0
- package/dist/hooks/ui/useResourceExport.d.ts +3 -1
- package/dist/hooks/ui/useResourceExport.js +5 -2
- package/dist/hooks/ui/useWorkflowTemplates.js +11 -6
- package/dist/hooks/useRealtimeSync.js +2 -1
- package/dist/hooks/useSyncOrchestrator.aiProfiles.test.js +8 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.d.ts +1 -0
- package/dist/hooks/useSyncOrchestrator.context-assets.test.js +228 -0
- package/dist/hooks/useSyncOrchestrator.d.ts +5 -4
- package/dist/hooks/useSyncOrchestrator.js +315 -860
- package/dist/hooks/useSyncOrchestrator.retry-closure.test.js +665 -8
- package/dist/hooks/useTaskMutations.js +45 -6
- package/dist/hooks/useTaskMutations.test.js +147 -0
- package/dist/hooks/useTaskforce.d.ts +11 -2
- package/dist/hooks/useTaskforce.js +290 -812
- package/dist/hooks/useWorkspaceSyncController.d.ts +1 -0
- package/dist/hooks/useWorkspaceSyncController.js +22 -10
- package/dist/hooks/workspace/useTaskforceApiRouting.test.js +17 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.d.ts +166 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +434 -0
- package/dist/hooks/workspace/workspaceLocalState.d.ts +19 -0
- package/dist/hooks/workspace/workspaceLocalState.js +100 -0
- package/dist/hooks/workspace/workspaceLocalState.test.d.ts +1 -0
- package/dist/hooks/workspace/workspaceLocalState.test.js +59 -0
- package/dist/localization/locales/en-US.js +2 -2
- package/dist/mcp/canonicalAssetHelpers.d.ts +70 -0
- package/dist/mcp/canonicalAssetHelpers.js +259 -0
- package/dist/mcp/clientIntegrations.d.ts +12 -1
- package/dist/mcp/clientIntegrations.js +161 -6
- package/dist/mcp/clientIntegrations.test.js +76 -4
- package/dist/mcp/documentHelpers.d.ts +100 -0
- package/dist/mcp/documentHelpers.js +161 -0
- package/dist/mcp/runtime.js +264 -505
- package/dist/mcp/runtime.test.js +353 -89
- package/dist/mcp/taskAttachmentHelpers.d.ts +41 -0
- package/dist/mcp/taskAttachmentHelpers.js +60 -0
- package/dist/mcp/taskAttachmentTypes.d.ts +37 -0
- package/dist/mcp/taskAttachmentTypes.js +1 -0
- package/dist/migrations/billingSchemaParity.test.js +3 -0
- package/dist/migrations/taskSchemaMigrations.js +15 -0
- package/dist/plugins/vite.js +1 -1
- package/dist/runtime/appGateDefinitions.d.ts +1 -1
- package/dist/runtime/appGateDefinitions.js +1 -1
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +48 -3
- package/dist/server/index.test.js +14 -2
- package/dist/server/mockStripe.d.ts +1 -0
- package/dist/server/mockStripe.js +2 -0
- package/dist/server/routes/admin.js +91 -40
- package/dist/server/routes/auth.d.ts +1 -0
- package/dist/server/routes/auth.js +231 -55
- package/dist/server/routes/billing.js +473 -39
- package/dist/server/routes/billing.test.js +972 -37
- package/dist/server/routes/shared.d.ts +30 -0
- package/dist/server/routes/shared.js +31 -3
- package/dist/server/routes/sync.integration.test.js +180 -0
- package/dist/server/routes/sync.js +125 -242
- package/dist/server/routes/tasks.js +49 -7
- package/dist/server/routes.js +11 -22
- package/dist/server/routes.test.js +530 -81
- package/dist/shared/taskActor.d.ts +10 -0
- package/dist/shared/taskActor.js +1 -0
- package/dist/sync/cloudSyncApi.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.js +4 -0
- package/dist/sync/collaborationSyncPayload.test.js +8 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -1
- package/dist/sync/collaborationSyncState.js +19 -0
- package/dist/sync/contentSyncState.d.ts +10 -0
- package/dist/sync/contentSyncState.js +277 -14
- package/dist/sync/syncApplyHandlers.d.ts +87 -1
- package/dist/sync/syncApplyHandlers.js +370 -8
- package/dist/sync/syncApplyHandlers.test.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.test.js +377 -0
- package/dist/sync/syncService.d.ts +9 -1
- package/dist/sync/syncService.js +57 -10
- package/dist/sync/syncService.test.js +48 -0
- package/dist/sync/workspacePullFeed.d.ts +11 -0
- package/dist/sync/workspacePullFeed.js +123 -68
- package/dist/sync/workspacePullFeed.test.js +180 -0
- package/dist/sync/workspaceSyncModel.d.ts +22 -1
- package/dist/sync/workspaceSyncModel.js +65 -0
- package/dist/sync/workspaceSyncModel.test.js +119 -0
- package/dist/types.d.ts +2 -10
- package/dist/ui/assets/AgentsModule-BLWVbuKP.js +1 -0
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-Dvi02Y7q.js → AnnotatedAttachmentWorkspace-BlS-cqnl.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-iys3zfca.js → DocumentWorkspace-BH_6DF6x.js} +1 -1
- package/dist/ui/assets/{InitiativesModule-DJvLJI3A.js → InitiativesModule-BjR6iBf9.js} +1 -1
- package/dist/ui/assets/{PlansPage-BlVC_lRq.css → PlansPage-C2dd2n1X.css} +1 -1
- package/dist/ui/assets/PlansPage-CSDQ4sLa.js +1 -0
- package/dist/ui/assets/TaskSettings-CEDy34Jo.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BguHeQaV.js → WorkflowsModule-DnFqdUME.js} +1 -1
- package/dist/ui/assets/index-DXUdtH1B.js +6 -0
- package/dist/ui/assets/index-Ii0B0rTO.css +1 -0
- package/dist/ui/assets/{vendor-icons-C8EED3Oh.js → vendor-icons-I7ZwG1z_.js} +1 -1
- package/dist/ui/index.html +4 -4
- package/dist/utils/accountProfileSummaryCache.d.ts +31 -0
- package/dist/utils/accountProfileSummaryCache.js +97 -0
- package/dist/utils/accountProfileSummaryCache.test.d.ts +1 -0
- package/dist/utils/accountProfileSummaryCache.test.js +63 -0
- package/dist/utils/taskActivity.js +4 -0
- package/dist/utils/taskActivity.test.js +25 -1
- package/package.json +5 -3
- package/scripts/playwright-auth.sh +4 -0
- package/dist/ui/assets/AgentsModule-JG5jc3he.js +0 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +0 -1
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +0 -8
- package/dist/ui/assets/index-MXeWoebC.css +0 -1
- package/dist/ui/assets/index-YJ4k5QNa.js +0 -6
- package/dist/utils/billingStatusCache.d.ts +0 -21
- package/dist/utils/billingStatusCache.js +0 -70
package/dist/ui/index.html
CHANGED
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
<link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
|
|
8
8
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />
|
|
9
9
|
<link rel="apple-touch-icon" href="/taskforce/favicon/apple-touch-icon.png" />
|
|
10
|
-
<title>Taskforce
|
|
11
|
-
<script type="module" crossorigin src="/taskforce/assets/index-
|
|
10
|
+
<title>Taskforce</title>
|
|
11
|
+
<script type="module" crossorigin src="/taskforce/assets/index-DXUdtH1B.js"></script>
|
|
12
12
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-react-CKJs5o3c.js">
|
|
13
|
-
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-
|
|
13
|
+
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-icons-I7ZwG1z_.js">
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-markdown-BUxTU7dS.js">
|
|
15
15
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-dnd-DRzYolkg.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/taskforce/assets/vendor-router-BbWMxlnO.js">
|
|
17
|
-
<link rel="stylesheet" crossorigin href="/taskforce/assets/index-
|
|
17
|
+
<link rel="stylesheet" crossorigin href="/taskforce/assets/index-Ii0B0rTO.css">
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
20
|
<div id="root"></div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export declare const ACCOUNT_PROFILE_SUMMARY_PATH = "/api/taskforce/account/profile-summary";
|
|
2
|
+
export type AccountProfileSummary = {
|
|
3
|
+
stripeStatus?: string | null;
|
|
4
|
+
stripeCustomerId?: string | null;
|
|
5
|
+
stripeSubscriptionId?: string | null;
|
|
6
|
+
stripePriceId?: string | null;
|
|
7
|
+
billingInterval?: 'month' | 'year' | null;
|
|
8
|
+
trialEnd?: string | null;
|
|
9
|
+
currentPeriodEnd?: string | null;
|
|
10
|
+
effectiveUntil?: string | null;
|
|
11
|
+
entitlementState?: string;
|
|
12
|
+
planVersionId?: string;
|
|
13
|
+
planId?: string;
|
|
14
|
+
planName?: string | null;
|
|
15
|
+
gate?: string | null;
|
|
16
|
+
message?: string | null;
|
|
17
|
+
billingConflictCode?: 'CHECKOUT_SESSION_EXPIRED' | 'CHECKOUT_SESSION_SUPERSEDED' | null;
|
|
18
|
+
planSelectionRequired?: boolean;
|
|
19
|
+
workspaceId?: string | null;
|
|
20
|
+
workspaceRole?: 'owner' | 'admin' | 'member' | 'read-only' | null;
|
|
21
|
+
canManageWorkspace?: boolean;
|
|
22
|
+
teamPlanMode?: 'personal' | 'team' | 'unknown';
|
|
23
|
+
teamManagementAllowed?: boolean;
|
|
24
|
+
};
|
|
25
|
+
export declare function invalidateAccountProfileSummaryCache(rawUrl: string, options?: {
|
|
26
|
+
identityKey?: string | null;
|
|
27
|
+
}): void;
|
|
28
|
+
export declare function loadAccountProfileSummaryCached(rawUrl: string, options?: {
|
|
29
|
+
force?: boolean;
|
|
30
|
+
identityKey?: string | null;
|
|
31
|
+
}): Promise<AccountProfileSummary | null>;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { logBootstrapDebug } from '../hooks/workspace/workspaceLocalState';
|
|
2
|
+
export const ACCOUNT_PROFILE_SUMMARY_PATH = '/api/taskforce/account/profile-summary';
|
|
3
|
+
const ACCOUNT_PROFILE_SUMMARY_CACHE_TTL_MS = 90_000;
|
|
4
|
+
const accountProfileSummaryCache = new Map();
|
|
5
|
+
function getTimingNow() {
|
|
6
|
+
return typeof performance !== 'undefined' ? performance.now() : Date.now();
|
|
7
|
+
}
|
|
8
|
+
function normalizeAccountProfileSummaryCacheKey(rawUrl, identityKey) {
|
|
9
|
+
const url = String(rawUrl || '').trim();
|
|
10
|
+
if (!url)
|
|
11
|
+
return '';
|
|
12
|
+
const normalizedIdentityKey = String(identityKey || '').trim();
|
|
13
|
+
return normalizedIdentityKey ? `${url}::${normalizedIdentityKey}` : url;
|
|
14
|
+
}
|
|
15
|
+
export function invalidateAccountProfileSummaryCache(rawUrl, options) {
|
|
16
|
+
accountProfileSummaryCache.delete(normalizeAccountProfileSummaryCacheKey(rawUrl, options?.identityKey));
|
|
17
|
+
}
|
|
18
|
+
export async function loadAccountProfileSummaryCached(rawUrl, options) {
|
|
19
|
+
const url = String(rawUrl || '').trim();
|
|
20
|
+
const cacheKey = normalizeAccountProfileSummaryCacheKey(rawUrl, options?.identityKey);
|
|
21
|
+
if (!cacheKey || !url)
|
|
22
|
+
return null;
|
|
23
|
+
const force = options?.force === true;
|
|
24
|
+
const now = Date.now();
|
|
25
|
+
const cached = accountProfileSummaryCache.get(cacheKey);
|
|
26
|
+
if (!force && cached?.payload !== undefined && (now - cached.fetchedAt) < ACCOUNT_PROFILE_SUMMARY_CACHE_TTL_MS) {
|
|
27
|
+
logBootstrapDebug('account_profile_summary_cache_hit', {
|
|
28
|
+
url,
|
|
29
|
+
cacheKey,
|
|
30
|
+
ageMs: now - cached.fetchedAt
|
|
31
|
+
});
|
|
32
|
+
return cached.payload;
|
|
33
|
+
}
|
|
34
|
+
if (!force && cached?.promise) {
|
|
35
|
+
logBootstrapDebug('account_profile_summary_request_reused', {
|
|
36
|
+
url,
|
|
37
|
+
cacheKey
|
|
38
|
+
});
|
|
39
|
+
return cached.promise;
|
|
40
|
+
}
|
|
41
|
+
const startedAt = getTimingNow();
|
|
42
|
+
const request = (async () => {
|
|
43
|
+
const res = await fetch(url, {
|
|
44
|
+
method: 'GET',
|
|
45
|
+
credentials: 'include'
|
|
46
|
+
});
|
|
47
|
+
if (!res.ok) {
|
|
48
|
+
throw new Error(String((await res.json().catch(() => ({})))?.error || 'Failed to load account summary.'));
|
|
49
|
+
}
|
|
50
|
+
const payload = await res.json().catch(() => ({}));
|
|
51
|
+
const normalized = (payload || null);
|
|
52
|
+
accountProfileSummaryCache.set(cacheKey, {
|
|
53
|
+
payload: normalized,
|
|
54
|
+
fetchedAt: Date.now(),
|
|
55
|
+
promise: null
|
|
56
|
+
});
|
|
57
|
+
logBootstrapDebug('account_profile_summary_loaded', {
|
|
58
|
+
url,
|
|
59
|
+
cacheKey,
|
|
60
|
+
durationMs: Math.round(getTimingNow() - startedAt),
|
|
61
|
+
fromCache: false
|
|
62
|
+
});
|
|
63
|
+
return normalized;
|
|
64
|
+
})().catch((error) => {
|
|
65
|
+
const stalePayload = cached?.payload ?? null;
|
|
66
|
+
if (stalePayload) {
|
|
67
|
+
accountProfileSummaryCache.set(cacheKey, {
|
|
68
|
+
payload: stalePayload,
|
|
69
|
+
fetchedAt: cached?.fetchedAt ?? Date.now(),
|
|
70
|
+
promise: null
|
|
71
|
+
});
|
|
72
|
+
logBootstrapDebug('account_profile_summary_failed', {
|
|
73
|
+
url,
|
|
74
|
+
cacheKey,
|
|
75
|
+
durationMs: Math.round(getTimingNow() - startedAt),
|
|
76
|
+
error: error instanceof Error ? error.message : String(error || 'Unknown error'),
|
|
77
|
+
returnedStale: true
|
|
78
|
+
});
|
|
79
|
+
return stalePayload;
|
|
80
|
+
}
|
|
81
|
+
accountProfileSummaryCache.delete(cacheKey);
|
|
82
|
+
logBootstrapDebug('account_profile_summary_failed', {
|
|
83
|
+
url,
|
|
84
|
+
cacheKey,
|
|
85
|
+
durationMs: Math.round(getTimingNow() - startedAt),
|
|
86
|
+
error: error instanceof Error ? error.message : String(error || 'Unknown error'),
|
|
87
|
+
returnedStale: false
|
|
88
|
+
});
|
|
89
|
+
throw error;
|
|
90
|
+
});
|
|
91
|
+
accountProfileSummaryCache.set(cacheKey, {
|
|
92
|
+
payload: cached?.payload ?? null,
|
|
93
|
+
fetchedAt: cached?.fetchedAt ?? 0,
|
|
94
|
+
promise: request
|
|
95
|
+
});
|
|
96
|
+
return request;
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
+
import { ACCOUNT_PROFILE_SUMMARY_PATH, invalidateAccountProfileSummaryCache, loadAccountProfileSummaryCached } from './accountProfileSummaryCache';
|
|
3
|
+
describe('accountProfileSummaryCache', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
vi.restoreAllMocks();
|
|
6
|
+
vi.useRealTimers();
|
|
7
|
+
invalidateAccountProfileSummaryCache(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
|
|
8
|
+
invalidateAccountProfileSummaryCache(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-b' });
|
|
9
|
+
});
|
|
10
|
+
it('keeps account summaries isolated per authenticated user', async () => {
|
|
11
|
+
const fetchMock = vi.fn()
|
|
12
|
+
.mockResolvedValueOnce({
|
|
13
|
+
ok: true,
|
|
14
|
+
json: async () => ({ planId: 'team-5', planVersionId: 'team-5-v1' })
|
|
15
|
+
})
|
|
16
|
+
.mockResolvedValueOnce({
|
|
17
|
+
ok: true,
|
|
18
|
+
json: async () => ({ planId: 'starter', planVersionId: 'starter-v1' })
|
|
19
|
+
});
|
|
20
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
21
|
+
const first = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
|
|
22
|
+
const second = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-b' });
|
|
23
|
+
expect(first?.planId).toBe('team-5');
|
|
24
|
+
expect(second?.planId).toBe('starter');
|
|
25
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
26
|
+
expect(fetchMock).toHaveBeenNthCalledWith(1, ACCOUNT_PROFILE_SUMMARY_PATH, expect.any(Object));
|
|
27
|
+
expect(fetchMock).toHaveBeenNthCalledWith(2, ACCOUNT_PROFILE_SUMMARY_PATH, expect.any(Object));
|
|
28
|
+
});
|
|
29
|
+
it('reuses the cached summary inside the cache window', async () => {
|
|
30
|
+
vi.useFakeTimers();
|
|
31
|
+
const fetchMock = vi.fn().mockResolvedValue({
|
|
32
|
+
ok: true,
|
|
33
|
+
json: async () => ({ planId: 'team-5', planVersionId: 'team-5-v1' })
|
|
34
|
+
});
|
|
35
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
36
|
+
const first = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
|
|
37
|
+
vi.advanceTimersByTime(30_000);
|
|
38
|
+
const second = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
|
|
39
|
+
expect(first?.planId).toBe('team-5');
|
|
40
|
+
expect(second?.planId).toBe('team-5');
|
|
41
|
+
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
42
|
+
});
|
|
43
|
+
it('returns the last successful summary when a forced refresh fails', async () => {
|
|
44
|
+
const fetchMock = vi.fn()
|
|
45
|
+
.mockResolvedValueOnce({
|
|
46
|
+
ok: true,
|
|
47
|
+
json: async () => ({ planId: 'team-5', planVersionId: 'team-5-v1', teamManagementAllowed: true })
|
|
48
|
+
})
|
|
49
|
+
.mockResolvedValueOnce({
|
|
50
|
+
ok: false,
|
|
51
|
+
json: async () => ({ error: 'Stripe unavailable' })
|
|
52
|
+
});
|
|
53
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
54
|
+
const warm = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, { identityKey: 'user-a' });
|
|
55
|
+
const refreshed = await loadAccountProfileSummaryCached(ACCOUNT_PROFILE_SUMMARY_PATH, {
|
|
56
|
+
identityKey: 'user-a',
|
|
57
|
+
force: true
|
|
58
|
+
});
|
|
59
|
+
expect(warm?.planId).toBe('team-5');
|
|
60
|
+
expect(refreshed).toEqual(warm);
|
|
61
|
+
expect(fetchMock).toHaveBeenCalledTimes(2);
|
|
62
|
+
});
|
|
63
|
+
});
|
|
@@ -5,6 +5,10 @@ function getActivityFieldLabel(field) {
|
|
|
5
5
|
return 'DESCRIPTION';
|
|
6
6
|
if (field === 'assignee')
|
|
7
7
|
return 'ASSIGNED TO';
|
|
8
|
+
if (field === 'workstreamId')
|
|
9
|
+
return 'WORKSTREAM';
|
|
10
|
+
if (field === 'initiativeId')
|
|
11
|
+
return 'INITIATIVE';
|
|
8
12
|
return field;
|
|
9
13
|
}
|
|
10
14
|
function formatValue(value, field, formatter) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { summarizeTaskChange } from './taskActivity';
|
|
2
|
+
import { summarizeTaskChange, summarizeTaskEvent } from './taskActivity';
|
|
3
3
|
describe('summarizeTaskChange', () => {
|
|
4
4
|
it('summarizes title edits without dumping before and after text', () => {
|
|
5
5
|
expect(summarizeTaskChange('title', {
|
|
@@ -58,4 +58,28 @@ describe('summarizeTaskChange', () => {
|
|
|
58
58
|
],
|
|
59
59
|
})).toBe('Checklist reordered');
|
|
60
60
|
});
|
|
61
|
+
it('uses readable labels for workstream relationship changes', () => {
|
|
62
|
+
expect(summarizeTaskChange('workstreamId', { from: null, to: 'workstream-1' }, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('WORKSTREAM: none -> WS-12');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
describe('summarizeTaskEvent', () => {
|
|
66
|
+
it('uses readable labels for task relationship events', () => {
|
|
67
|
+
expect(summarizeTaskEvent({
|
|
68
|
+
id: 'event-1',
|
|
69
|
+
taskId: 'task-1',
|
|
70
|
+
workspaceId: 'default',
|
|
71
|
+
action: 'task-relationship-changed',
|
|
72
|
+
actor: 'user',
|
|
73
|
+
actorType: 'human',
|
|
74
|
+
createdAt: '2026-04-12T21:42:00.000Z',
|
|
75
|
+
details: {
|
|
76
|
+
changes: {
|
|
77
|
+
workstreamId: {
|
|
78
|
+
from: null,
|
|
79
|
+
to: 'workstream-1',
|
|
80
|
+
},
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
}, (_field, value) => value === 'workstream-1' ? 'WS-12' : undefined)).toBe('Workstream: none -> WS-12');
|
|
84
|
+
});
|
|
61
85
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@taskforcehq/taskforce",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.304",
|
|
4
4
|
"description": "Mission Command center for AI-powered task management (beta - under active development)",
|
|
5
5
|
"author": "Taskforce HQ <hello@taskforcehq.ai> (https://taskforcehq.ai)",
|
|
6
6
|
"license": "MIT",
|
|
@@ -37,6 +37,8 @@
|
|
|
37
37
|
"perf": "sh scripts/run-dev-proxy.sh performance",
|
|
38
38
|
"stage": "sh scripts/run-dev-proxy.sh staging",
|
|
39
39
|
"prod": "TASKFORCE_RUNTIME_MODE=local TASKFORCE_CLOUD_ENVIRONMENT=production TASKFORCE_CLOUD_PROXY_BASE_URL=https://app.taskforcehq.ai VITE_TASKFORCE_BASE_URL=https://app.taskforcehq.ai npm run dev:runtime",
|
|
40
|
+
"deploy:staging": "bash scripts/deploy-staging.sh",
|
|
41
|
+
"deploy:prod": "bash scripts/deploy-prod.sh",
|
|
40
42
|
"dev:runtime": "concurrently \"npm run dev:app\" \"TASKFORCE_RUNTIME_MODE=local sh scripts/with-esbuild.sh node --import tsx src/cli.ts open 5174\"",
|
|
41
43
|
"stage:direct": "TASKFORCE_RUNTIME_MODE=local TASKFORCE_CLOUD_ENVIRONMENT=staging TASKFORCE_CLOUD_PROXY_BASE_URL=https://staging-app.taskforcehq.ai VITE_TASKFORCE_BASE_URL=https://staging-app.taskforcehq.ai npm run dev:runtime",
|
|
42
44
|
"perf:direct": "TASKFORCE_RUNTIME_MODE=local TASKFORCE_CLOUD_ENVIRONMENT=performance TASKFORCE_CLOUD_PROXY_BASE_URL=https://performance-app.taskforcehq.ai VITE_TASKFORCE_BASE_URL=https://performance-app.taskforcehq.ai npm run dev:runtime",
|
|
@@ -60,8 +62,6 @@
|
|
|
60
62
|
"check:localization-strings": "node scripts/check-localization-strings.mjs",
|
|
61
63
|
"version:next": "node scripts/release-version.mjs --dry-run",
|
|
62
64
|
"version:bump": "node scripts/release-version.mjs",
|
|
63
|
-
"deploy:staging": "bash scripts/deploy-staging.sh",
|
|
64
|
-
"deploy:prod": "bash scripts/deploy-prod.sh",
|
|
65
65
|
"test:localization": "vitest run src/__tests__/localizationCatalogs.test.ts",
|
|
66
66
|
"check:runtime-boundaries": "node scripts/check-runtime-boundaries.mjs",
|
|
67
67
|
"test:workflow-readiness": "npm run check:workflows && vitest src/__tests__/workflowSimulation.test.ts src/__tests__/workflowExportMatrix.test.ts src/__tests__/workflowExportSnapshots.test.ts src/__tests__/workflowDocumentFixtures.test.ts src/__tests__/structuredCommentValidation.test.ts src/__tests__/structuredDocValidation.test.ts src/__tests__/workflowV2Policy.test.ts src/__tests__/workflowGeneration.test.ts src/__tests__/templates.test.ts",
|
|
@@ -78,6 +78,8 @@
|
|
|
78
78
|
"test:e2e:realtime:reconnect": "npm run test:e2e:realtime -- tests/e2e/realtime-reconnect.spec.ts",
|
|
79
79
|
"playwright:auth:bootstrap": "node scripts/bootstrap-playwright-auth.mjs",
|
|
80
80
|
"playwright:auth": "sh scripts/playwright-auth.sh",
|
|
81
|
+
"playwright:interactive": "playwright test -c playwright.interactive.config.ts tests/e2e/interactive.smoke.spec.ts",
|
|
82
|
+
"playwright:interactive:headed": "playwright test --headed -c playwright.interactive.config.ts tests/e2e/interactive.smoke.spec.ts",
|
|
81
83
|
"qa:open": "node scripts/qa-open.mjs",
|
|
82
84
|
"check:env": "node --import tsx scripts/check-env.ts --target=server --strict=true",
|
|
83
85
|
"check:env:soak": "node --import tsx scripts/check-env.ts --target=soak --strict=true",
|
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
set -eu
|
|
3
3
|
|
|
4
4
|
LOCAL_ENV_FILE="${TASKFORCE_PLAYWRIGHT_ENV_FILE:-/Users/rossburke/.codex/taskforce-playwright.env}"
|
|
5
|
+
REPO_E2E_ENV_FILE="${TASKFORCE_PLAYWRIGHT_REPO_E2E_ENV_FILE:-.env.e2e}"
|
|
5
6
|
|
|
6
7
|
if [ -f "$LOCAL_ENV_FILE" ]; then
|
|
7
8
|
# shellcheck disable=SC1090
|
|
8
9
|
. "$LOCAL_ENV_FILE"
|
|
10
|
+
elif [ -f "$REPO_E2E_ENV_FILE" ]; then
|
|
11
|
+
# shellcheck disable=SC1090
|
|
12
|
+
. "$REPO_E2E_ENV_FILE"
|
|
9
13
|
fi
|
|
10
14
|
|
|
11
15
|
exec node scripts/bootstrap-playwright-auth.mjs "$@"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as n,j as e}from"./vendor-react-CKJs5o3c.js";import{p as T,u as C,v as w,w as S,t as s,x as M}from"./index-YJ4k5QNa.js";import{h as L,B as A,k as I,a8 as H,aG as B,aH as D,b as R}from"./vendor-icons-C8EED3Oh.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";import"./vendor-router-BbWMxlnO.js";function F({workspaceId:h}){const[x,k]=n.useState([]),[j,y]=n.useState(!1),[c,P]=n.useState(null),[p,m]=n.useState(null),[d,g]=n.useState(null),N=n.useCallback(async()=>{if(h){y(!0);try{const a=await(await fetch(`/api/taskforce/workspace/assignee-options?workspaceId=${encodeURIComponent(h)}`,{credentials:"include"})).json().catch(()=>({})),t=Array.isArray(a?.assignees)?a.assignees.filter(i=>i.kind==="agent").map(i=>({id:String(i.value||""),name:String(i.label||i.value||"Unknown Agent"),username:String(i.username||i.value||""),icon:String(i.icon||"Bot"),color:String(i.color||"#6B7280"),kind:String(i.kind||"agent"),description:typeof i.description=="string"?i.description:null,role:typeof i.role=="string"?i.role:null,provider:typeof i.provider=="string"?i.provider:null,model:typeof i.model=="string"?i.model:null,surfaceType:T(i.surfaceType),createdAt:String(i.createdAt||""),updatedAt:String(i.updatedAt||i.createdAt||""),lastActiveAt:typeof i.lastActiveAt=="string"?i.lastActiveAt:null})):[];k(t)}finally{y(!1)}}},[h]);n.useEffect(()=>{N()},[N]);const f=n.useMemo(()=>{const r=new Map;for(const a of x){const t=C(a.surfaceType);r.has(t)||r.set(t,new Map);const i=r.get(t),v=a.name.toLowerCase();i.has(v)||i.set(v,[]),i.get(v).push(a)}return w.map(a=>({section:a,label:S(a),groups:Array.from(r.get(a)?.entries()||[]).map(([t,i])=>({groupId:`${a}:${t}`,section:a,sectionLabel:S(a),profiles:i,primaryProfile:i[0]}))})).filter(a=>a.groups.length>0)},[x]),o=n.useMemo(()=>f.flatMap(r=>r.groups),[f]),l=n.useMemo(()=>o.find(r=>r.groupId===d)||o[0]||null,[o,d]);n.useEffect(()=>{if(!o.length){d!==null&&g(null);return}(!d||!o.some(r=>r.groupId===d))&&g(o[0].groupId)},[o,d]);const b=async(r,a)=>{m(null);try{const t=await fetch("/api/taskforce/workspace/ai-profiles/merge",{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({keepId:r,mergeId:a})}),i=await t.json().catch(()=>({}));if(!t.ok){m({type:"error",message:String(i?.error||"Failed to merge AI profiles.")});return}P(null),m({type:"success",message:"AI profiles merged."}),await N()}catch{m({type:"error",message:"Failed to merge AI profiles."})}},u=r=>{const a=String(r||"").trim();if(!a)return"Unknown";const t=Date.parse(a);return Number.isFinite(t)?new Date(t).toLocaleString(void 0,{dateStyle:"medium",timeStyle:"short"}):a};return e.jsx("div",{className:`${s.standalonePage} ${s.standaloneContent}`,style:{overflow:"auto"},children:e.jsx("div",{className:s.settingsContent,style:{maxWidth:960,margin:"0 auto",width:"100%"},children:e.jsx("div",{className:s.settingGroup,children:e.jsxs("div",{children:[e.jsx("h4",{className:s.settingSubTitle,children:"Registered AI Profiles"}),e.jsx("p",{className:`${s.settingsHint} ${s.marginBottom12}`,children:"AI agents register profiles when connecting via MCP. Merge duplicates created when a token was lost."}),j&&e.jsxs("div",{className:s.settingsHint,children:[e.jsx(L,{size:13,className:s.spinner})," Loading profiles…"]}),!j&&x.length===0&&e.jsx("div",{className:s.settingsHint,children:"No AI profiles registered yet."}),!j&&f.length>0&&e.jsxs("div",{className:s.aiProfilesExplorer,children:[e.jsx("div",{className:s.aiProfilesListPane,children:e.jsx("div",{className:s.aiProfilesList,children:f.map(r=>e.jsxs("div",{className:s.aiProfilesSection,children:[e.jsx("div",{className:s.aiProfilesSectionHeader,children:r.label}),r.groups.map(a=>{const t=l?.groupId===a.groupId,i=a.primaryProfile;return e.jsxs("button",{type:"button",className:`${s.aiProfileGroup} ${a.profiles.length>1?s.aiProfileGroupDuplicate:""} ${t?s.aiProfileGroupSelected:""}`,onClick:()=>g(a.groupId),"aria-pressed":t,children:[e.jsxs("div",{className:s.aiProfileGroupHeader,children:[e.jsx(A,{size:13,style:{color:i.color}}),e.jsx("span",{className:s.aiProfileName,children:i.name}),a.profiles.length>1&&e.jsxs("span",{className:s.aiProfileDuplicateBadge,children:[e.jsx(I,{size:11})," ",a.profiles.length," duplicates"]})]}),e.jsxs("div",{className:s.settingsHint,style:{marginTop:6},children:["@",i.username]}),(i.surfaceType||i.role||i.provider||i.model)&&e.jsxs("div",{className:s.aiProfileMetaRow,children:[i.surfaceType&&e.jsx("span",{className:s.aiProfileSurfaceBadge,children:M(i.surfaceType)}),i.role&&e.jsx("span",{children:i.role}),i.provider&&e.jsx("span",{children:i.provider}),i.model&&e.jsx("span",{children:i.model})]}),i.description&&e.jsx("div",{className:s.settingsHint,style:{marginTop:4},children:i.description})]},a.groupId)})]},r.section))})}),l&&e.jsx("div",{className:s.aiProfileDetailPane,children:e.jsxs("div",{className:s.aiProfileDetailCard,children:[e.jsxs("div",{className:s.aiProfileDetailHero,children:[e.jsx("div",{className:s.aiProfileDetailIcon,style:{color:l.primaryProfile.color},children:e.jsx(A,{size:18})}),e.jsxs("div",{className:s.aiProfileDetailHeading,children:[e.jsx("div",{className:s.aiProfileDetailTitleRow,children:e.jsx("h5",{className:s.aiProfileDetailTitle,children:l.primaryProfile.name})}),e.jsxs("div",{className:s.aiProfileDetailMetaRow,children:[e.jsxs("span",{className:s.aiProfileDetailHandle,children:["@",l.primaryProfile.username]}),e.jsx("span",{className:s.aiProfileDetailSectionBadge,children:l.sectionLabel}),l.profiles.length>1&&e.jsxs("span",{className:s.aiProfileDetailLinkedCount,children:[l.profiles.length," linked"]})]})]})]}),l.primaryProfile.description&&e.jsx("div",{className:s.aiProfileDetailDescription,children:l.primaryProfile.description}),e.jsxs("div",{className:s.aiProfileDetailGrid,children:[e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(H,{size:12})," Role"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:l.primaryProfile.role||"Not set"})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(B,{size:12})," Provider"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:[l.primaryProfile.provider,l.primaryProfile.model].filter(Boolean).join(" · ")||"Not set"})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(D,{size:12})," Last active"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:u(l.primaryProfile.lastActiveAt)})]}),e.jsxs("div",{className:s.aiProfileDetailStat,children:[e.jsxs("span",{className:s.aiProfileDetailStatLabel,children:[e.jsx(D,{size:12})," Updated"]}),e.jsx("span",{className:s.aiProfileDetailStatValue,children:u(l.primaryProfile.updatedAt)})]})]}),e.jsxs("div",{className:s.aiProfileInstanceSection,children:[e.jsxs("div",{className:s.aiProfileInstanceSectionHeader,children:[e.jsx("span",{children:"Profile instances"}),e.jsx("span",{className:s.settingsHint,children:"Choose a keeper here if duplicates need to be merged."})]}),e.jsx("div",{className:s.aiProfileInstanceList,children:l.profiles.map(r=>e.jsxs("div",{className:s.aiProfileInstanceCard,children:[e.jsxs("div",{className:s.aiProfileInstanceTopRow,children:[e.jsxs("div",{children:[e.jsxs("div",{className:s.aiProfileInstanceName,children:["@",r.username]}),e.jsx("div",{className:s.aiProfileIdChip,children:r.id})]}),l.profiles.length>1&&c?.keepId===r.id&&e.jsx("span",{className:s.aiProfileKeepBadge,children:"Keeping"})]}),e.jsxs("div",{className:s.aiProfileInstanceMeta,children:[e.jsxs("span",{children:["Created ",u(r.createdAt)]}),e.jsxs("span",{children:["Updated ",u(r.updatedAt)]})]}),l.profiles.length>1&&c?.keepId!==r.id&&e.jsx("button",{className:s.secondaryHeaderBtn,title:"Keep this profile, merge others into it",onClick:()=>{const a=l.profiles.find(t=>t.id!==r.id)?.id;a&&P({keepId:r.id,mergeId:a})},children:"Keep this"})]},r.id))}),c&&l.profiles.some(r=>r.id===c.keepId)&&e.jsxs("div",{className:s.aiProfileMergeActions,children:[e.jsx("button",{className:s.dangerBtn,onClick:()=>{b(c.keepId,c.mergeId)},children:"Merge duplicates"}),e.jsx("button",{className:s.secondaryHeaderBtn,onClick:()=>P(null),children:"Cancel"})]})]})]})})]}),p&&e.jsxs("div",{className:`${p.type==="success"?s.successMessage:s.errorMessage} ${s.marginTop12}`,children:[p.type==="success"?e.jsx(R,{size:14}):e.jsx(I,{size:14}),p.message]})]})})})})}export{F as AgentsModule};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{r as a,j as e}from"./vendor-react-CKJs5o3c.js";import{t as ue,y as de,z,B as pe,C as me}from"./index-YJ4k5QNa.js";import{aI as ge,h as fe,U as he}from"./vendor-icons-C8EED3Oh.js";import{u as ye,a as _e}from"./vendor-router-BbWMxlnO.js";import"./vendor-markdown-BUxTU7dS.js";import"./vendor-dnd-DRzYolkg.js";const be="_plansWrapper_b8yvs_1",ve="_contentFrame_b8yvs_28",Se="_shellOwnsScroll_b8yvs_36",Ce="_withChrome_b8yvs_42",ke="_siteVariant_b8yvs_46",xe="_title_b8yvs_54",we="_subtitle_b8yvs_63",Ie="_header_b8yvs_67",je="_planCard_b8yvs_79",Ne="_grid_b8yvs_92",Te="_planName_b8yvs_98",Re="_featureText_b8yvs_102",Pe="_currentBadge_b8yvs_106",Be="_navAction_b8yvs_110",$e="_backBtn_b8yvs_111",Ee="_priceRows_b8yvs_122",Ue="_headerBar_b8yvs_139",Ae="_headerActions_b8yvs_148",Fe="_debugMeta_b8yvs_175",Le="_clickablePlanCard_b8yvs_204",Me="_activePlanCard_b8yvs_219",Ve="_callToAction_b8yvs_272",Oe="_primaryCta_b8yvs_284",Ye="_secondaryCta_b8yvs_295",qe="_currentCta_b8yvs_307",ze="_featuresList_b8yvs_321",De="_priceRow_b8yvs_122",Ge="_priceRowAmount_b8yvs_346",He="_priceRowInterval_b8yvs_352",We="_ctaRow_b8yvs_357",Qe="_featureItem_b8yvs_363",Ke="_featureIcon_b8yvs_369",Je="_emptyState_b8yvs_383",Xe="_marketingEmptyState_b8yvs_392",Ze="_emptyBadge_b8yvs_420",et="_emptyHighlights_b8yvs_435",tt="_loader_b8yvs_457",nt="_spinner_b8yvs_467",rt="_statusBanner_b8yvs_520",st="_successBanner_b8yvs_528",at="_errorBanner_b8yvs_534",n={plansWrapper:be,contentFrame:ve,shellOwnsScroll:Se,withChrome:Ce,siteVariant:ke,title:xe,subtitle:we,header:Ie,planCard:je,grid:Ne,planName:Te,featureText:Re,currentBadge:Pe,navAction:Be,backBtn:$e,priceRows:Ee,headerBar:Ue,headerActions:Ae,debugMeta:Fe,clickablePlanCard:Le,activePlanCard:Me,callToAction:Ve,primaryCta:Oe,secondaryCta:Ye,currentCta:qe,featuresList:ze,priceRow:De,priceRowAmount:Ge,priceRowInterval:He,ctaRow:We,featureItem:Qe,featureIcon:Ke,emptyState:Je,marketingEmptyState:Xe,emptyBadge:Ze,emptyHighlights:et,loader:tt,spinner:nt,statusBanner:rt,successBanner:st,errorBanner:at};function it(){const r=globalThis.__DEBUG_MODE__;return typeof r=="boolean"?r:!1}function ot(r){const i=String(r?.environment||"").trim(),_=String(r?.runtimeMode||"").trim();return!i&&!_?null:i&&_?`${i} (${_})`:i||_||null}function ct(r,i){if(r==null||!i)return"Contact Sales";try{return new Intl.NumberFormat("en-US",{style:"currency",currency:i.toUpperCase(),minimumFractionDigits:0}).format(r/100)}catch{return`${i.toUpperCase()} ${r/100}`}}function lt(){return"$0.00"}function ie(r){return r?r.pricingType==="free"?!0:r.unitAmount!=null&&r.currency!=null:!1}function oe(r){return r?r.pricingType==="free"?lt():ct(r.unitAmount,r.currency):"Unavailable"}function ce(r){const i=[r.pricing.month,r.pricing.year].filter(Boolean);if(i.some(m=>m.pricingType==="free"))return 0;const _=i.map(m=>m.pricingType==="stripe"&&typeof m.unitAmount=="number"?m.unitAmount:null).filter(m=>m!=null);return _.length>0?Math.min(..._):Number.POSITIVE_INFINITY}function ut(r){const i=String(r||"").trim().toLowerCase();return i==="active"||i==="trialing"||i==="grace"}function dt({heading:r,subtitle:i,variant:_="app",chrome:m,footer:P,isAuthenticated:B,authSessionResolved:D=!0,currentPlanId:V,currentPlanVersionId:C,currentEntitlementState:X,canManageCurrentPlan:d=!0,pricingEndpoint:f,statusBanner:b,onBack:G,backLabel:H,topActions:$,shellOwnsScroll:W=!1,showPageBackButton:c=!0,fallbackPricingSource:U=null,onSelectPlan:S}){const[w,E]=a.useState([]),[I,Q]=a.useState(null),[A,O]=a.useState(!0),[F,k]=a.useState(null),[K,Z]=a.useState(0);a.useEffect(()=>{let s=!0;return(async()=>{const y=String(f||"/api/taskforce/public/pricing").trim()||"/api/taskforce/public/pricing";s&&(O(!0),k(null));try{const l=await fetch(y,{method:"GET",credentials:"include"}),u=await l.json().catch(()=>({}));if(l.ok){s&&(E(Array.isArray(u?.plans)?u.plans:[]),Q(u?.pricingSource&&typeof u.pricingSource=="object"?u.pricingSource:null));return}throw new Error(String(u?.error||`Failed to load pricing (${l.status})`))}catch(l){if(s){const u=l instanceof Error?l.message:"Failed to load pricing";k(u||"Failed to load pricing"),Q(null)}}finally{s&&O(!1)}})(),()=>{s=!1}},[f,K]);const h=a.useMemo(()=>[...w].sort((s,g)=>{const y=ce(s),l=ce(g);if(y!==l)return y-l;const u=s.defaultVersion?.versionNumber||0,p=g.defaultVersion?.versionNumber||0;return u!==p?u-p:String(s.displayName||s.planId).localeCompare(String(g.displayName||g.planId))}),[w]),Y=a.useMemo(()=>ot(I||U),[U,I]),q=`${n.plansWrapper} ${W?n.shellOwnsScroll:"tf-scrollbar"} ${_==="site"?n.siteVariant:""} ${m?n.withChrome:""}`.trim(),j=s=>W?e.jsxs(e.Fragment,{children:[m,s,P]}):e.jsxs("div",{className:ue.standaloneWrapper,"data-theme":"dark",children:[m,s,P]});return j(A||!D?e.jsx(e.Fragment,{children:e.jsx("div",{className:q,children:e.jsx("div",{className:n.contentFrame,children:e.jsxs("div",{className:n.loader,children:[e.jsx(fe,{size:48,className:n.spinner}),e.jsx("p",{children:"Loading plans..."})]})})})}):F?e.jsx(e.Fragment,{children:e.jsx("div",{className:q,children:e.jsx("div",{className:n.contentFrame,children:e.jsxs("div",{className:n.emptyState,children:[e.jsx("h2",{children:"Connecting to plan pricing..."}),e.jsx("p",{children:F}),e.jsx("button",{className:n.backBtn,onClick:()=>Z(s=>s+1),children:"Retry"})]})})})}):e.jsx(e.Fragment,{children:e.jsx("div",{className:q,children:e.jsxs("div",{className:n.contentFrame,children:[e.jsxs("div",{className:n.header,children:[!m&&(c||$)&&e.jsxs("div",{className:n.headerBar,children:[e.jsx("div",{children:c?e.jsx("button",{className:n.backBtn,onClick:G,children:H}):null}),e.jsx("div",{className:n.headerActions,children:$})]}),e.jsx("h1",{className:n.title,children:r}),i&&e.jsx("p",{className:n.subtitle,children:i}),it()&&Y?e.jsxs("p",{className:n.debugMeta,children:["Pricing source: ",Y]}):null,b&&e.jsx("div",{className:`${n.statusBanner} ${b.type==="success"?n.successBanner:n.errorBanner}`,children:b.message})]}),h.length===0?e.jsxs("div",{className:`${n.emptyState} ${n.marketingEmptyState}`,children:[e.jsx("span",{className:n.emptyBadge,children:"Coming Soon"}),e.jsx("h2",{children:"Paid plans are getting their final polish."}),e.jsx("p",{children:"Taskforce pricing is on the way with flexible options for solo operators, teams, and larger rollouts. Check back soon for launch tiers, feature bundles, and early access details."}),e.jsxs("div",{className:n.emptyHighlights,"aria-label":"Upcoming pricing highlights",children:[e.jsx("span",{children:"Launch-ready tiers"}),e.jsx("span",{children:"Team billing controls"}),e.jsx("span",{children:"Feature-based packaging"})]})]}):e.jsx("div",{className:n.grid,children:h.map(s=>{const g=s.pricing.month,y=s.pricing.year,l=ie(g),u=ie(y),p=String(s.defaultVersion?.planVersionId||"").trim(),x=!!(p&&C&&C===p&&ut(X))||!C&&!!(V&&V===s.planId),N=l?"month":u?"year":null,L=N==="year"?y:g,T=!!p&&(x?d:!!N),J=()=>{if(p){if(x){if(!d)return;S({planId:s.planId,planVersionId:p,interval:N||"month",pricingType:L?.pricingType||"stripe",isCurrentPlan:!0});return}N&&S({planId:s.planId,planVersionId:p,interval:N,pricingType:L?.pricingType||"stripe",isCurrentPlan:!1})}};return e.jsxs("div",{className:`${n.planCard} ${x?n.activePlanCard:""} ${T?n.clickablePlanCard:""}`,"data-testid":`plan-card-${s.planId}`,role:T?"button":void 0,tabIndex:T?0:void 0,onClick:T?J:void 0,onKeyDown:T?t=>{(t.key==="Enter"||t.key===" ")&&(t.preventDefault(),J())}:void 0,children:[x&&e.jsx("span",{className:n.currentBadge,children:"Current Plan"}),e.jsx("h3",{className:n.planName,children:s.displayName}),e.jsx("div",{className:n.featuresList,children:Array.isArray(s.features)&&s.features.length>0?s.features.map((t,o)=>e.jsxs("div",{className:n.featureItem,children:[e.jsx(ge,{className:n.featureIcon}),e.jsx("span",{className:n.featureText,children:String(t.featureKey||"").replace(/[_\-.]+/g," ").replace(/\b\w/g,v=>v.toUpperCase())})]},`${t.featureKey}-${o}`)):e.jsx("span",{className:n.featureText,children:"No additional features included"})}),e.jsxs("div",{className:n.priceRows,children:[e.jsxs("div",{className:n.priceRow,children:[e.jsx("span",{className:n.priceRowAmount,children:oe(g)}),e.jsx("span",{className:n.priceRowInterval,children:"/ month"})]}),e.jsxs("div",{className:n.priceRow,children:[e.jsx("span",{className:n.priceRowAmount,children:oe(y)}),e.jsx("span",{className:n.priceRowInterval,children:"/ year"})]})]}),x?e.jsx("button",{className:`${n.callToAction} ${n.currentCta}`,"data-testid":`plan-manage-${s.planId}`,onClick:t=>{t.stopPropagation(),d&&S({planId:s.planId,planVersionId:p,interval:N||"month",pricingType:L?.pricingType||"stripe",isCurrentPlan:!0})},disabled:!p||!d,children:B&&d?"Manage Plan":"Current Plan"}):e.jsxs("div",{className:n.ctaRow,children:[e.jsx("button",{className:`${n.callToAction} ${n.secondaryCta}`,"data-testid":`plan-select-${s.planId}-month`,onClick:t=>{t.stopPropagation(),S({planId:s.planId,planVersionId:p,interval:"month",pricingType:g?.pricingType||"stripe",isCurrentPlan:!1})},disabled:!p||!l,children:g?.pricingType==="free"?B?"Select Free":"Get Started Free":B?"Choose Monthly":"Get Started Monthly"}),e.jsx("button",{className:`${n.callToAction} ${n.primaryCta}`,"data-testid":`plan-select-${s.planId}-year`,onClick:t=>{t.stopPropagation(),S({planId:s.planId,planVersionId:p,interval:"year",pricingType:y?.pricingType||"stripe",isCurrentPlan:!1})},disabled:!p||!u,children:y?.pricingType==="free"?B?"Select Free":"Get Started Free":B?"Choose Yearly":"Get Started Yearly"})]})]},s.planId)})})]})})}))}const pt=1600,mt=new Set(["active","trialing","grace"]),M={planSelectionRequired:"Choose a plan to continue.",misconfigured:"Signup monetization is misconfigured. Contact support or a system administrator.",missingEntitlement:"No entitlement is linked to this account."};function gt(r){return r==="year"?"year":"month"}function le(r){const i=String(r||"").trim();return i?i.replace(/\/+$/,""):""}function ne(r={}){const i=new URLSearchParams;i.set("screen","plans");for(const[_,m]of Object.entries(r)){const P=String(m||"").trim();P&&i.set(_,P)}return`/?${i.toString()}`}function St({isAuthenticated:r,runtimeMode:i,authSessionResolved:_,currentTheme:m="dark",projectName:P,currentWorkspaceId:B,apiBaseUrl:D,connectedEnvironmentSource:V,embedded:C=!1,shellOwnsScroll:X=!1}){const d=ye(),f=_e(),[b,G]=a.useState(null),[H,$]=a.useState(!1),[W,c]=a.useState(null),U=a.useRef(!1),S=a.useRef(null),w=a.useMemo(()=>new URLSearchParams(f.search),[f.search]),E=String(w.get("checkout")||"").trim().toLowerCase(),I=String(w.get("gate")||"").trim().toLowerCase(),Q=String(w.get("planId")||"").trim(),A=String(w.get("planVersionId")||"").trim(),O=gt(w.get("interval")),F=String(w.get("session_id")||"").trim(),k=a.useMemo(()=>le(D),[D]),K=a.useMemo(()=>le(V),[V]),Z=a.useMemo(()=>({environment:de(K||k),runtimeMode:i==="cloud"?"cloud":"local"}),[k,K,i]),h=a.useCallback(t=>{const o=String(t||"").trim();return k?`${k}${o.startsWith("/")?o:`/${o}`}`:o},[k]),Y=a.useCallback(()=>{const t=new URLSearchParams(f.search);t.delete("screen"),t.delete("gate"),t.delete("checkout"),t.delete("planId"),t.delete("planVersionId"),t.delete("interval");const o=t.toString();d(`/${o?`?${o}`:""}${f.hash||""}`)},[f.hash,f.search,d]),q=a.useMemo(()=>[k?`${k}/api/taskforce/public/pricing`:"","/api/taskforce/public/pricing"].map(o=>String(o||"").trim()).filter((o,v,R)=>o&&R.indexOf(o)===v),[k]),j=a.useMemo(()=>{const t=String(b?.entitlementState||"").trim().toLowerCase();return mt.has(t)},[b?.entitlementState]),s=a.useMemo(()=>j?null:I==="plan_selection_required"?M.planSelectionRequired:I==="misconfigured"?M.misconfigured:I==="missing_entitlement"?M.missingEntitlement:null,[I,j]),g=a.useCallback(()=>{const t=new URLSearchParams(f.search);t.delete("checkout"),t.delete("session_id"),t.delete("gate"),t.delete("planId"),t.delete("planVersionId"),t.delete("interval");const o=t.toString();d(`${f.pathname}${o?`?${o}`:""}${f.hash||""}`,{replace:!0})},[f.hash,f.pathname,f.search,d]),y=a.useCallback(()=>{S.current!=null&&window.clearTimeout(S.current),S.current=window.setTimeout(()=>{g(),S.current=null},pt)},[g]),l=a.useCallback(async()=>{const t=h("/api/taskforce/billing/status");if(!r){G(null),z(t);return}try{const o=await pe(t);G(o)}catch{}},[h,r]),u=a.useCallback(async(t,o)=>{if(!t){c({type:"error",message:"Selected plan is missing a default plan version."});return}$(!0),c(null);try{const v=await fetch(h("/api/taskforce/billing/checkout-session"),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({planVersionId:t,interval:o})}),R=await v.json().catch(()=>({}));if(!v.ok){c({type:"error",message:String(R?.error||"Unable to start checkout for selected plan.")});return}const ee=String(R?.checkoutState||"").trim().toLowerCase();if(ee==="free"){c({type:"success",message:"Plan selected. Your access is ready."}),z(h("/api/taskforce/billing/status")),await l();return}if(ee==="updated"){c({type:"success",message:String(R?.message||"Your subscription was updated successfully.")}),z(h("/api/taskforce/billing/status")),await l();return}if(ee==="already_current_plan"){c({type:"success",message:String(R?.message||"You are already on this plan.")}),z(h("/api/taskforce/billing/status")),await l();return}const re=String(R?.returnBaseUrl||"").trim();if(i==="local"&&re)try{const te=window.location.origin,ae=new URL(re,te).origin;if(ae!==te){c({type:"error",message:`Checkout return is misconfigured for this runtime. Expected ${te} but got ${ae}.`});return}}catch{c({type:"error",message:"Checkout return URL is invalid for this runtime."});return}const se=String(R?.url||"").trim();if(!se){c({type:"error",message:"Checkout did not return a redirect URL."});return}window.location.assign(se)}catch{c({type:"error",message:"Unable to start checkout for selected plan."})}finally{$(!1)}},[h,l]),p=a.useCallback(async t=>{if(!t)return!1;try{const o=await fetch(h("/api/taskforce/billing/checkout-session/confirm"),{method:"POST",credentials:"include",headers:{"Content-Type":"application/json"},body:JSON.stringify({sessionId:t})}),v=await o.json().catch(()=>({}));return o.ok?(z(h("/api/taskforce/billing/status")),await l(),!0):(c({type:"error",message:String(v?.error||"Unable to confirm checkout completion.")}),!1)}catch{return c({type:"error",message:"Unable to confirm checkout completion."}),!1}},[h,l]),x=a.useCallback(async()=>{if(!r){d(`/login?mode=login&next=${encodeURIComponent(ne())}`);return}$(!0),c(null);try{const t=await fetch(h("/api/taskforce/billing/portal-session"),{method:"POST",credentials:"include"}),o=await t.json().catch(()=>({}));if(!t.ok){c({type:"error",message:String(o?.error||"Failed to open billing portal.")});return}const v=String(o?.url||"").trim();if(!v){c({type:"error",message:"Portal session did not return a redirect URL."});return}window.location.assign(v)}catch{c({type:"error",message:"Failed to open billing portal."})}finally{$(!1)}},[h,r,d]);a.useEffect(()=>{l()},[l]),a.useEffect(()=>{j&&I==="missing_entitlement"&&g()},[g,I,j]),a.useEffect(()=>()=>{S.current!=null&&window.clearTimeout(S.current)},[]),a.useEffect(()=>{if(E==="success"){U.current=!0,c({type:"success",message:"Checkout completed successfully. Finalizing your plan..."}),(async()=>(F&&await p(F)||await l(),c({type:"success",message:"Checkout completed successfully."}),y()))();return}if(E==="cancel"){c({type:"error",message:"Checkout was canceled. No charge was made."}),y();return}if((E==="start"||E==="free")&&r&&A&&!U.current){U.current=!0,u(A,O);return}if(s){c({type:"error",message:s});return}c(t=>!t||t.type!=="error"?t:t.message===M.planSelectionRequired||t.message===M.misconfigured||t.message===M.missingEntitlement?null:t)},[E,p,s,r,l,O,A,F,y,u]);const N=a.useCallback(async t=>{if(!r){const o=t.pricingType==="free"?"free":"start",v=ne({checkout:o,planId:t.planId,planVersionId:t.planVersionId,interval:t.interval});d(`/login?mode=register&planId=${encodeURIComponent(t.planId)}&planVersionId=${encodeURIComponent(t.planVersionId)}&interval=${encodeURIComponent(t.interval)}&next=${encodeURIComponent(v)}`);return}if(t.isCurrentPlan){if(!b?.stripeCustomerId){c({type:"success",message:"Your current plan does not use Stripe billing."});return}await x();return}if(t.pricingType==="free"){await u(t.planVersionId,t.interval);return}await u(t.planVersionId,t.interval)},[r,d,x,u]),L=a.useCallback(()=>{if(C){Y();return}if(window.history.length>1){d(-1);return}d("/")},[Y,C,d]),T=C?e.jsx(e.Fragment,{children:r&&b?.stripeCustomerId&&e.jsx("button",{className:n.navAction,onClick:()=>{x()},disabled:H,children:"Manage billing"})}):e.jsxs(e.Fragment,{children:[e.jsx("button",{className:n.navAction,onClick:L,children:"Back"}),r&&b?.stripeCustomerId&&e.jsx("button",{className:n.navAction,onClick:()=>{x()},disabled:H,children:"Manage billing"}),!r&&e.jsxs("button",{className:n.navAction,onClick:()=>d(`/login?mode=login&next=${encodeURIComponent(ne())}`),children:[e.jsx(he,{size:16,style:{marginRight:"8px",verticalAlign:"text-bottom"}}),"Sign In"]})]}),J=C?null:e.jsx(me,{projectName:P,currentWorkspaceId:B,runtimeMode:i==="cloud"?"cloud":"local",theme:m,actions:T});return e.jsx(dt,{chrome:J,heading:"Subscription Plans",subtitle:"Manage your subscription and explore available features. Your current plan is highlighted below.",isAuthenticated:r,authSessionResolved:_,currentPlanId:String(b?.planId||Q||"").trim()||null,currentPlanVersionId:String(b?.planVersionId||A||"").trim()||null,currentEntitlementState:String(b?.entitlementState||"").trim()||null,canManageCurrentPlan:!!b?.stripeCustomerId,pricingEndpoint:q[0],statusBanner:W,onBack:L,backLabel:C?"Back to Board":"Back to App",topActions:C?T:null,shellOwnsScroll:X,showPageBackButton:!C,fallbackPricingSource:Z,onSelectPlan:N})}export{St as PlansPage};
|