@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.
- package/README.md +19 -0
- package/dist/Taskforce.module.css +26 -2
- package/dist/TaskforceCore.filter-persistence.test.js +1 -1
- package/dist/TaskforceCore.js +13 -24
- package/dist/TaskforceCore.test.js +167 -152
- package/dist/components/features/TaskSettings.js +14 -82
- package/dist/components/features/TaskSettings.test.js +33 -70
- package/dist/components/features/settings/TaxonomyLibraryManager.js +8 -2
- package/dist/components/features/settings/TaxonomyLibraryManager.test.js +1 -1
- package/dist/components/task/TaskActionHeader.js +6 -3
- package/dist/components/task/TaskCard.js +6 -3
- package/dist/components/task/TaskForm.js +2 -2
- package/dist/components/task/TaskForm.test.js +41 -2
- package/dist/components/ui/ReferenceBadgeButton.js +1 -1
- package/dist/components/views/PlansPage.js +9 -8
- package/dist/components/views/StandaloneLayout.js +490 -207
- package/dist/components/views/panels/FilterToolbar.d.ts +2 -0
- package/dist/components/views/panels/FilterToolbarControls.d.ts +1 -0
- package/dist/components/views/panels/FilterToolbarControls.js +1 -1
- package/dist/components/views/panels/PlanningDrawer.js +19 -2
- package/dist/components/views/standalone/modals/AccountHubModal.d.ts +43 -0
- package/dist/components/views/standalone/modals/AccountHubModal.js +22 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/CreateWorkspaceConfirmModal.js +8 -0
- package/dist/components/views/standalone/modals/EditProfileModal.d.ts +26 -0
- package/dist/components/views/standalone/modals/EditProfileModal.js +10 -0
- package/dist/components/views/standalone/modals/HelpModal.d.ts +9 -0
- package/dist/components/views/standalone/modals/HelpModal.js +8 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.d.ts +13 -0
- package/dist/components/views/standalone/modals/ScheduleWarningModal.js +6 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.d.ts +61 -0
- package/dist/components/views/standalone/modals/SyncStatusModal.js +38 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.d.ts +64 -0
- package/dist/components/views/standalone/modals/TeamManagementModal.js +10 -0
- package/dist/components/views/standalone/modals/types.d.ts +2 -0
- package/dist/components/views/standalone/modals/types.js +1 -0
- package/dist/components/views/teamManagementAccess.d.ts +1 -1
- package/dist/components/views/teamManagementAccess.js +2 -2
- package/dist/components/views/teamManagementAccess.test.js +3 -3
- package/dist/config/deployment.js +2 -2
- package/dist/config/deployment.test.js +5 -3
- package/dist/core/AiProfileService.js +7 -2
- package/dist/core/AiProfiles.test.js +19 -0
- package/dist/core/SyncReconciliationService.d.ts +2 -0
- package/dist/core/SyncReconciliationService.js +3 -0
- package/dist/core/TaskAuditTimeline.test.js +40 -0
- package/dist/core/Taskforce.d.ts +49 -0
- package/dist/core/Taskforce.ids.test.js +133 -0
- package/dist/core/Taskforce.js +241 -78
- package/dist/core/UserProfileAvatarDrafts.test.d.ts +1 -0
- package/dist/core/UserProfileAvatarDrafts.test.js +102 -0
- package/dist/core/taskReferencePolicy.d.ts +28 -0
- package/dist/core/taskReferencePolicy.js +189 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/hooks/tasks/useTaskFormActions.js +3 -0
- package/dist/hooks/useTaskData.js +16 -11
- package/dist/hooks/useTaskData.test.js +127 -0
- package/dist/hooks/useTaskforce.d.ts +5 -0
- package/dist/hooks/useTaskforce.js +63 -15
- package/dist/hooks/useTaskforce.sync-behavior.test.js +185 -6
- package/dist/mcp/aiProfileBootstrap.d.ts +29 -0
- package/dist/mcp/aiProfileBootstrap.js +80 -0
- package/dist/mcp/aiProfileBootstrap.test.d.ts +1 -0
- package/dist/mcp/aiProfileBootstrap.test.js +68 -0
- package/dist/mcp/clientIntegrations.d.ts +2 -8
- package/dist/mcp/clientIntegrations.js +21 -40
- package/dist/mcp/clientIntegrations.test.js +28 -2
- package/dist/mcp/clientProfileDefaults.d.ts +12 -0
- package/dist/mcp/clientProfileDefaults.js +37 -0
- package/dist/mcp/runtime.d.ts +10 -7
- package/dist/mcp/runtime.js +45 -23
- package/dist/mcp/runtime.test.js +56 -3
- package/dist/migrations/taskSchemaMigrations.js +53 -1
- package/dist/qaOpen.test.d.ts +1 -0
- package/dist/qaOpen.test.js +56 -0
- package/dist/server/index.cookieProxy.test.js +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +15 -1
- package/dist/server/index.test.js +14 -1
- package/dist/server/routes/admin.js +25 -22
- package/dist/server/routes/auth.d.ts +5 -0
- package/dist/server/routes/auth.js +350 -16
- package/dist/server/routes/billing.js +11 -1
- package/dist/server/routes/billing.test.js +32 -2
- package/dist/server/routes/documents.js +6 -1
- package/dist/server/routes/shared.d.ts +1 -1
- package/dist/server/routes/shared.js +11 -3
- package/dist/server/routes/sync.integration.test.js +130 -0
- package/dist/server/routes/sync.js +226 -1924
- package/dist/server/routes/tasks.js +18 -4
- package/dist/server/routes.js +26 -19
- package/dist/server/routes.test.js +352 -6
- package/dist/shared/runtimeContract.d.ts +1 -1
- package/dist/sync/collaborationSyncPayload.d.ts +17 -0
- package/dist/sync/collaborationSyncPayload.js +131 -0
- package/dist/sync/collaborationSyncPayload.test.d.ts +1 -0
- package/dist/sync/collaborationSyncPayload.test.js +104 -0
- package/dist/sync/collaborationSyncState.d.ts +15 -0
- package/dist/sync/collaborationSyncState.js +60 -0
- package/dist/sync/contentSyncPayload.d.ts +45 -0
- package/dist/sync/contentSyncPayload.js +104 -0
- package/dist/sync/contentSyncPayload.test.d.ts +1 -0
- package/dist/sync/contentSyncPayload.test.js +104 -0
- package/dist/sync/contentSyncState.d.ts +88 -0
- package/dist/sync/contentSyncState.js +743 -0
- package/dist/sync/planningSyncPayload.d.ts +9 -0
- package/dist/sync/planningSyncPayload.js +82 -0
- package/dist/sync/planningSyncPayload.test.d.ts +1 -0
- package/dist/sync/planningSyncPayload.test.js +68 -0
- package/dist/sync/syncApplyHandlers.d.ts +83 -0
- package/dist/sync/syncApplyHandlers.js +445 -0
- package/dist/sync/taskSyncPayload.d.ts +33 -0
- package/dist/sync/taskSyncPayload.js +126 -0
- package/dist/sync/taskSyncPayload.test.d.ts +1 -0
- package/dist/sync/taskSyncPayload.test.js +54 -0
- package/dist/sync/workspacePullFeed.d.ts +28 -0
- package/dist/sync/workspacePullFeed.js +301 -0
- package/dist/sync/workspacePullFeed.test.d.ts +1 -0
- package/dist/sync/workspacePullFeed.test.js +166 -0
- package/dist/sync/workspaceSyncModel.d.ts +4 -1
- package/dist/sync/workspaceSyncModel.js +9 -2
- package/dist/sync/workspaceSyncModel.test.js +29 -0
- package/dist/test/setup.js +1 -1
- package/dist/types.d.ts +1 -0
- package/dist/ui/assets/{AgentsModule-TVmCgizS.js → AgentsModule-JG5jc3he.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-CAyhnPeR.js → AnnotatedAttachmentWorkspace-Dvi02Y7q.js} +1 -1
- package/dist/ui/assets/{DocumentWorkspace-BOCw5e4D.js → DocumentWorkspace-iys3zfca.js} +2 -2
- package/dist/ui/assets/{InitiativesModule-CN0r02Oi.js → InitiativesModule-DJvLJI3A.js} +1 -1
- package/dist/ui/assets/PlansPage-BMPlpFpw.js +1 -0
- package/dist/ui/assets/TaskSettings-Bqd4cCzW.js +8 -0
- package/dist/ui/assets/{TaskSettings-BwB9NtmJ.css → TaskSettings-CnIBL_Eb.css} +1 -1
- package/dist/ui/assets/{WorkflowsModule-CY9R2zPB.js → WorkflowsModule-BguHeQaV.js} +1 -1
- package/dist/ui/assets/index-MXeWoebC.css +1 -0
- package/dist/ui/assets/index-YJ4k5QNa.js +6 -0
- package/dist/ui/assets/{vendor-icons-QZyhEwgT.js → vendor-icons-C8EED3Oh.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/utils/billingStatusCache.d.ts +21 -0
- package/dist/utils/billingStatusCache.js +70 -0
- package/dist/utils/taskActivity.js +17 -4
- package/dist/utils/taskActivity.test.js +27 -0
- package/dist/utils/taskNormalization.js +10 -2
- package/dist/utils/taskReferences.d.ts +11 -1
- package/dist/utils/taskReferences.js +33 -1
- package/package.json +4 -1
- package/scripts/deploy-prod.sh +41 -0
- package/scripts/deploy-staging.sh +23 -0
- package/scripts/qa-open-lib.mjs +175 -0
- package/scripts/qa-open-lib.mjs.d.ts +49 -0
- package/scripts/qa-open.mjs +352 -0
- package/dist/ui/assets/PlansPage-FcQSNLZd.js +0 -1
- package/dist/ui/assets/TaskSettings-Cw5zYD1L.js +0 -8
- package/dist/ui/assets/index-Bzqy5QGm.js +0 -6
- package/dist/ui/assets/index-DxRiI3tY.css +0 -1
|
@@ -50,7 +50,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
50
50
|
localStorage.clear();
|
|
51
51
|
vi.stubGlobal('fetch', vi.fn((url) => {
|
|
52
52
|
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
53
|
-
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: '
|
|
53
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } }) });
|
|
54
54
|
}
|
|
55
55
|
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
56
56
|
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, state: {} }) });
|
|
@@ -168,7 +168,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
168
168
|
it('shows the header sync badge as off when local runtime is signed out', async () => {
|
|
169
169
|
vi.stubGlobal('fetch', vi.fn((url) => {
|
|
170
170
|
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
171
|
-
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: '
|
|
171
|
+
return Promise.resolve({ ok: true, json: () => Promise.resolve({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } }) });
|
|
172
172
|
}
|
|
173
173
|
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
174
174
|
return Promise.resolve({ ok: true, json: () => Promise.resolve({ success: true, state: {} }) });
|
|
@@ -1569,7 +1569,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1569
1569
|
expect(screen.getByText('CLOUD.EXAMPLE.COM')).toBeInTheDocument();
|
|
1570
1570
|
});
|
|
1571
1571
|
}, 10000);
|
|
1572
|
-
it('allows local runtime to open shared
|
|
1572
|
+
it('allows local runtime to open the shared cloud-backed register route', async () => {
|
|
1573
1573
|
const fetchMock = vi.fn((url) => {
|
|
1574
1574
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
1575
1575
|
return Promise.resolve({
|
|
@@ -1619,130 +1619,6 @@ describe('TaskforceCore Integration', () => {
|
|
|
1619
1619
|
});
|
|
1620
1620
|
expect(container.querySelector('[aria-pressed]')).not.toBeInTheDocument();
|
|
1621
1621
|
});
|
|
1622
|
-
it('shows debug registration autofill only when debug mode is enabled', async () => {
|
|
1623
|
-
const fetchMock = vi.fn((url) => {
|
|
1624
|
-
if (url.includes('/api/taskforce/auth/session')) {
|
|
1625
|
-
return Promise.resolve({
|
|
1626
|
-
ok: true,
|
|
1627
|
-
json: () => Promise.resolve({
|
|
1628
|
-
authenticated: false,
|
|
1629
|
-
runtimeMode: 'local',
|
|
1630
|
-
authRequiredForApi: false
|
|
1631
|
-
})
|
|
1632
|
-
});
|
|
1633
|
-
}
|
|
1634
|
-
if (url.includes('/api/taskforce/config')) {
|
|
1635
|
-
return Promise.resolve({
|
|
1636
|
-
ok: true,
|
|
1637
|
-
json: () => Promise.resolve({
|
|
1638
|
-
...mockConfig,
|
|
1639
|
-
runtimeMode: 'local',
|
|
1640
|
-
authRequiredForApi: false,
|
|
1641
|
-
setupState: {
|
|
1642
|
-
globalSetupState: 'present',
|
|
1643
|
-
workspaceSetupState: 'present',
|
|
1644
|
-
runtimeMode: 'local',
|
|
1645
|
-
workspaceId: 'workspace-local-active',
|
|
1646
|
-
mode: 'core',
|
|
1647
|
-
forceSetup: false,
|
|
1648
|
-
forceGlobalSetup: false,
|
|
1649
|
-
forceWorkspaceSetup: false
|
|
1650
|
-
}
|
|
1651
|
-
})
|
|
1652
|
-
});
|
|
1653
|
-
}
|
|
1654
|
-
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
1655
|
-
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
1656
|
-
}
|
|
1657
|
-
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
1658
|
-
});
|
|
1659
|
-
vi.stubGlobal('fetch', fetchMock);
|
|
1660
|
-
const firstRender = render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
1661
|
-
await waitFor(() => {
|
|
1662
|
-
expect(screen.getByText('Create your Taskforce account.')).toBeInTheDocument();
|
|
1663
|
-
});
|
|
1664
|
-
expect(screen.queryByRole('button', { name: 'Fill Form' })).not.toBeInTheDocument();
|
|
1665
|
-
firstRender.unmount();
|
|
1666
|
-
globalThis.__DEBUG_MODE__ = true;
|
|
1667
|
-
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
1668
|
-
expect(await screen.findByRole('button', { name: 'Fill Form' })).toBeInTheDocument();
|
|
1669
|
-
});
|
|
1670
|
-
it('fills and submits generated debug registration credentials through the normal register flow', async () => {
|
|
1671
|
-
globalThis.__DEBUG_MODE__ = true;
|
|
1672
|
-
const user = userEvent.setup();
|
|
1673
|
-
const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(1735689600000);
|
|
1674
|
-
const fetchMock = vi.fn((input, init) => {
|
|
1675
|
-
const url = String(input);
|
|
1676
|
-
if (url.includes('/api/taskforce/auth/session')) {
|
|
1677
|
-
return Promise.resolve({
|
|
1678
|
-
ok: true,
|
|
1679
|
-
json: () => Promise.resolve({
|
|
1680
|
-
authenticated: false,
|
|
1681
|
-
runtimeMode: 'local',
|
|
1682
|
-
authRequiredForApi: false
|
|
1683
|
-
})
|
|
1684
|
-
});
|
|
1685
|
-
}
|
|
1686
|
-
if (url.includes('/api/taskforce/config')) {
|
|
1687
|
-
return Promise.resolve({
|
|
1688
|
-
ok: true,
|
|
1689
|
-
json: () => Promise.resolve({
|
|
1690
|
-
...mockConfig,
|
|
1691
|
-
runtimeMode: 'local',
|
|
1692
|
-
authRequiredForApi: false,
|
|
1693
|
-
setupState: {
|
|
1694
|
-
globalSetupState: 'present',
|
|
1695
|
-
workspaceSetupState: 'present',
|
|
1696
|
-
runtimeMode: 'local',
|
|
1697
|
-
workspaceId: 'workspace-local-active',
|
|
1698
|
-
mode: 'core',
|
|
1699
|
-
forceSetup: false,
|
|
1700
|
-
forceGlobalSetup: false,
|
|
1701
|
-
forceWorkspaceSetup: false
|
|
1702
|
-
}
|
|
1703
|
-
})
|
|
1704
|
-
});
|
|
1705
|
-
}
|
|
1706
|
-
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
1707
|
-
return Promise.resolve({ ok: true, json: () => Promise.resolve({ templates: [] }) });
|
|
1708
|
-
}
|
|
1709
|
-
if (url.includes('/api/taskforce/auth/register')) {
|
|
1710
|
-
return Promise.resolve({
|
|
1711
|
-
ok: true,
|
|
1712
|
-
json: () => Promise.resolve({
|
|
1713
|
-
success: true,
|
|
1714
|
-
workspaceSetupRequired: false,
|
|
1715
|
-
verificationRequired: true,
|
|
1716
|
-
emailSent: true
|
|
1717
|
-
})
|
|
1718
|
-
});
|
|
1719
|
-
}
|
|
1720
|
-
return Promise.resolve({ ok: true, json: () => Promise.resolve({}) });
|
|
1721
|
-
});
|
|
1722
|
-
vi.stubGlobal('fetch', fetchMock);
|
|
1723
|
-
render(_jsx(MemoryRouter, { initialEntries: ['/login?mode=register&next=%2F'], children: _jsx(TaskforceCore, {}) }));
|
|
1724
|
-
const emailInput = await screen.findByPlaceholderText('Email');
|
|
1725
|
-
const displayNameInput = screen.getByPlaceholderText('Display name');
|
|
1726
|
-
const passwordInput = screen.getByPlaceholderText('Password');
|
|
1727
|
-
await user.click(screen.getByRole('button', { name: 'Fill Form' }));
|
|
1728
|
-
expect(String(emailInput.value)).toMatch(/^debug\+[a-z0-9]+@example\.com$/);
|
|
1729
|
-
expect(String(displayNameInput.value)).toMatch(/^Debug [A-Z0-9]{4}$/);
|
|
1730
|
-
expect(passwordInput).toHaveValue('DebugPass123!');
|
|
1731
|
-
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
1732
|
-
await waitFor(() => {
|
|
1733
|
-
const registerCall = fetchMock.mock.calls.find((call) => String(call[0]).includes('/api/taskforce/auth/register'));
|
|
1734
|
-
expect(registerCall).toBeTruthy();
|
|
1735
|
-
const payload = JSON.parse(String(registerCall?.[1]?.body || '{}'));
|
|
1736
|
-
expect(String(registerCall?.[0] || '')).toContain('/api/taskforce/auth/register');
|
|
1737
|
-
const init = registerCall?.[1];
|
|
1738
|
-
expect(String(init?.method || '')).toBe('POST');
|
|
1739
|
-
expect(String(init?.credentials || '')).toBe('include');
|
|
1740
|
-
expect(payload.email).toBe(String(emailInput.value));
|
|
1741
|
-
expect(payload.displayName).toBe(String(displayNameInput.value));
|
|
1742
|
-
expect(payload.password).toBe('DebugPass123!');
|
|
1743
|
-
});
|
|
1744
|
-
nowSpy.mockRestore();
|
|
1745
|
-
});
|
|
1746
1622
|
it('does not switch to verify mode after registration when verification is not required', async () => {
|
|
1747
1623
|
const user = userEvent.setup();
|
|
1748
1624
|
const fetchMock = vi.fn((input) => {
|
|
@@ -1752,7 +1628,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
1752
1628
|
ok: true,
|
|
1753
1629
|
json: () => Promise.resolve({
|
|
1754
1630
|
authenticated: false,
|
|
1755
|
-
runtimeMode: '
|
|
1631
|
+
runtimeMode: 'cloud',
|
|
1756
1632
|
authRequiredForApi: false
|
|
1757
1633
|
})
|
|
1758
1634
|
});
|
|
@@ -1762,12 +1638,12 @@ describe('TaskforceCore Integration', () => {
|
|
|
1762
1638
|
ok: true,
|
|
1763
1639
|
json: () => Promise.resolve({
|
|
1764
1640
|
...mockConfig,
|
|
1765
|
-
runtimeMode: '
|
|
1641
|
+
runtimeMode: 'cloud',
|
|
1766
1642
|
authRequiredForApi: false,
|
|
1767
1643
|
setupState: {
|
|
1768
1644
|
globalSetupState: 'present',
|
|
1769
1645
|
workspaceSetupState: 'present',
|
|
1770
|
-
runtimeMode: '
|
|
1646
|
+
runtimeMode: 'cloud',
|
|
1771
1647
|
workspaceId: 'workspace-local-active',
|
|
1772
1648
|
mode: 'core',
|
|
1773
1649
|
forceSetup: false,
|
|
@@ -1804,12 +1680,12 @@ describe('TaskforceCore Integration', () => {
|
|
|
1804
1680
|
await user.type(screen.getByPlaceholderText('Password'), 'Password123!');
|
|
1805
1681
|
await user.click(screen.getByRole('button', { name: 'Create Account' }));
|
|
1806
1682
|
await waitFor(() => {
|
|
1807
|
-
expect(screen.
|
|
1683
|
+
expect(screen.getByText('Subscription Plans')).toBeInTheDocument();
|
|
1808
1684
|
});
|
|
1809
1685
|
expect(screen.queryByPlaceholderText('Verification token')).not.toBeInTheDocument();
|
|
1810
1686
|
expect(screen.queryByRole('button', { name: 'Create Account' })).not.toBeInTheDocument();
|
|
1811
1687
|
});
|
|
1812
|
-
it('allows returning to sign-in/register from forgot mode', async () => {
|
|
1688
|
+
it('allows returning to sign-in/register from forgot mode in local runtime', async () => {
|
|
1813
1689
|
const user = userEvent.setup();
|
|
1814
1690
|
const fetchMock = vi.fn((url) => {
|
|
1815
1691
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
@@ -1872,14 +1748,14 @@ describe('TaskforceCore Integration', () => {
|
|
|
1872
1748
|
json: () => Promise.resolve({
|
|
1873
1749
|
success: true,
|
|
1874
1750
|
config: {
|
|
1875
|
-
baseUrl: '
|
|
1876
|
-
apiBaseUrl: '
|
|
1877
|
-
cloudAuthBaseUrl: '
|
|
1878
|
-
runtimeMode: '
|
|
1751
|
+
baseUrl: 'https://app.taskforce.example',
|
|
1752
|
+
apiBaseUrl: 'https://app.taskforce.example/api',
|
|
1753
|
+
cloudAuthBaseUrl: 'https://app.taskforce.example',
|
|
1754
|
+
runtimeMode: 'cloud',
|
|
1879
1755
|
authSource: 'cloud',
|
|
1880
|
-
workspaceMode: '
|
|
1881
|
-
workspaceSwitchingEnabled:
|
|
1882
|
-
cloudAuthViaLocalProxy:
|
|
1756
|
+
workspaceMode: 'multi-cloud',
|
|
1757
|
+
workspaceSwitchingEnabled: true,
|
|
1758
|
+
cloudAuthViaLocalProxy: false
|
|
1883
1759
|
}
|
|
1884
1760
|
})
|
|
1885
1761
|
});
|
|
@@ -1889,8 +1765,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
1889
1765
|
ok: true,
|
|
1890
1766
|
json: () => Promise.resolve({
|
|
1891
1767
|
authenticated: false,
|
|
1892
|
-
runtimeMode: '
|
|
1893
|
-
authRequiredForApi:
|
|
1768
|
+
runtimeMode: 'cloud',
|
|
1769
|
+
authRequiredForApi: true
|
|
1894
1770
|
})
|
|
1895
1771
|
});
|
|
1896
1772
|
}
|
|
@@ -1899,12 +1775,12 @@ describe('TaskforceCore Integration', () => {
|
|
|
1899
1775
|
ok: true,
|
|
1900
1776
|
json: () => Promise.resolve({
|
|
1901
1777
|
...mockConfig,
|
|
1902
|
-
runtimeMode: '
|
|
1903
|
-
authRequiredForApi:
|
|
1778
|
+
runtimeMode: 'cloud',
|
|
1779
|
+
authRequiredForApi: true,
|
|
1904
1780
|
setupState: {
|
|
1905
1781
|
globalSetupState: 'present',
|
|
1906
1782
|
workspaceSetupState: 'present',
|
|
1907
|
-
runtimeMode: '
|
|
1783
|
+
runtimeMode: 'cloud',
|
|
1908
1784
|
workspaceId: 'workspace-local-active',
|
|
1909
1785
|
mode: 'core',
|
|
1910
1786
|
forceSetup: false,
|
|
@@ -3078,10 +2954,10 @@ describe('TaskforceCore Integration', () => {
|
|
|
3078
2954
|
});
|
|
3079
2955
|
expect(cloudWorkspaceCreated).toBe(false);
|
|
3080
2956
|
});
|
|
3081
|
-
it('shows local runtime Sign In
|
|
2957
|
+
it('shows local runtime Sign In action and routes to shared login', async () => {
|
|
3082
2958
|
const standaloneLayoutPath = path.join(process.cwd(), 'src/components/views/StandaloneLayout.tsx');
|
|
3083
2959
|
const standaloneLayoutSource = fs.readFileSync(standaloneLayoutPath, 'utf8');
|
|
3084
|
-
expect(standaloneLayoutSource).toContain('Sign In
|
|
2960
|
+
expect(standaloneLayoutSource).toContain('Sign In');
|
|
3085
2961
|
expect(standaloneLayoutSource).toContain('openSharedLoginRoute(\'login\')');
|
|
3086
2962
|
expect(standaloneLayoutSource).toContain('navigate(`/login?mode=${mode}&next=${encodeURIComponent(target)}`)');
|
|
3087
2963
|
});
|
|
@@ -3093,14 +2969,15 @@ describe('TaskforceCore Integration', () => {
|
|
|
3093
2969
|
expect(standaloneLayoutSource).toContain("const canManageCloudWorkspaces = runtimeMode === 'cloud' && workspaceSwitchingEnabled && isAuthenticated;");
|
|
3094
2970
|
expect(standaloneLayoutSource).toContain("const canAccessCloudTeamManagement = isAuthenticated && (runtimeMode === 'cloud' || cloudAuthConfigured);");
|
|
3095
2971
|
expect(standaloneLayoutSource).toContain("const teamManagementWorkspaceId = String(authWorkspaceId || currentWorkspaceId || '').trim();");
|
|
3096
|
-
expect(standaloneLayoutSource).toContain(
|
|
2972
|
+
expect(standaloneLayoutSource).toContain("fetchTaskforceApi('/api/taskforce/account/team-management-access'");
|
|
2973
|
+
expect(standaloneLayoutSource).toContain('computeCanOpenTeamManagement(canAccessCloudTeamManagement, teamWorkspaceRole, teamPlanMode)');
|
|
3097
2974
|
expect(useTaskforceSource).toContain("const [authWorkspaceId, setAuthWorkspaceId] = useState<string>('');");
|
|
3098
2975
|
});
|
|
3099
2976
|
it('routes local plans and billing UI through the connected cloud billing base', () => {
|
|
3100
2977
|
const standaloneLayoutPath = path.join(process.cwd(), 'src/components/views/StandaloneLayout.tsx');
|
|
3101
2978
|
const standaloneLayoutSource = fs.readFileSync(standaloneLayoutPath, 'utf8');
|
|
3102
2979
|
expect(standaloneLayoutSource).toContain("const billingApiBaseUrl = useMemo(() => {");
|
|
3103
|
-
expect(standaloneLayoutSource).toContain("
|
|
2980
|
+
expect(standaloneLayoutSource).toContain("loadBillingStatusCached(billingStatusUrl)");
|
|
3104
2981
|
expect(standaloneLayoutSource).toContain("apiBaseUrl={billingApiBaseUrl}");
|
|
3105
2982
|
});
|
|
3106
2983
|
it('uses shared /login flow for local sign in instead of a duplicate modal', async () => {
|
|
@@ -3125,6 +3002,8 @@ describe('TaskforceCore Integration', () => {
|
|
|
3125
3002
|
const appMainPath = path.join(process.cwd(), 'apps/app/main.tsx');
|
|
3126
3003
|
const appMainSource = fs.readFileSync(appMainPath, 'utf8');
|
|
3127
3004
|
expect(useTaskforceSource).toContain("return 'https://app.taskforcehq.ai';");
|
|
3005
|
+
expect(useTaskforceSource).toContain('const preferredCloudAuthBaseUrl = isLoopbackHost');
|
|
3006
|
+
expect(useTaskforceSource).toContain("? (runtimeResolvedCloudAuthBaseUrl || explicitConfiguredCloudAuthBaseUrl)");
|
|
3128
3007
|
expect(appMainSource).toContain('resolveClientDeploymentConfig');
|
|
3129
3008
|
expect(appMainSource).toContain('return undefined;');
|
|
3130
3009
|
});
|
|
@@ -3204,6 +3083,110 @@ describe('TaskforceCore Integration', () => {
|
|
|
3204
3083
|
const pullCalls = fetchMock.mock.calls.filter((call) => typeof call[0] === 'string' && String(call[0]).includes('/sync/workspace/pull'));
|
|
3205
3084
|
expect(pullCalls.length).toBe(0);
|
|
3206
3085
|
});
|
|
3086
|
+
it('keeps local workspace data loaded after signing out from the account menu', async () => {
|
|
3087
|
+
const user = userEvent.setup();
|
|
3088
|
+
let signedIn = true;
|
|
3089
|
+
const fetchMock = vi.fn((url) => {
|
|
3090
|
+
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
3091
|
+
return jsonResponse({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } });
|
|
3092
|
+
}
|
|
3093
|
+
if (url.includes('https://cloud.example.com/api/taskforce/auth/session')) {
|
|
3094
|
+
return jsonResponse({
|
|
3095
|
+
authenticated: signedIn,
|
|
3096
|
+
runtimeMode: 'local',
|
|
3097
|
+
authRequiredForApi: false,
|
|
3098
|
+
email: signedIn ? 'member@example.com' : '',
|
|
3099
|
+
userId: signedIn ? 'user-member' : 'anonymous',
|
|
3100
|
+
workspaceId: 'workspace-local-active',
|
|
3101
|
+
betaAccess: true
|
|
3102
|
+
});
|
|
3103
|
+
}
|
|
3104
|
+
if (url.includes('https://cloud.example.com/api/taskforce/auth/logout')) {
|
|
3105
|
+
signedIn = false;
|
|
3106
|
+
return jsonResponse({ success: true });
|
|
3107
|
+
}
|
|
3108
|
+
if (url.includes('/api/taskforce/ui-state?key=app')) {
|
|
3109
|
+
return jsonResponse({ success: true, state: {} });
|
|
3110
|
+
}
|
|
3111
|
+
if (url.includes('/api/taskforce/ui-state?key=workspace-sync')) {
|
|
3112
|
+
return jsonResponse({
|
|
3113
|
+
success: true,
|
|
3114
|
+
state: {
|
|
3115
|
+
version: 2,
|
|
3116
|
+
enabled: false,
|
|
3117
|
+
phase: 'idle',
|
|
3118
|
+
pullCursor: null,
|
|
3119
|
+
lastPullAt: null,
|
|
3120
|
+
lastPushAt: null,
|
|
3121
|
+
lastSyncedAt: null
|
|
3122
|
+
}
|
|
3123
|
+
});
|
|
3124
|
+
}
|
|
3125
|
+
if (url.includes('/api/taskforce/config')) {
|
|
3126
|
+
return jsonResponse({
|
|
3127
|
+
...mockConfig,
|
|
3128
|
+
runtimeMode: 'local',
|
|
3129
|
+
authRequiredForApi: false,
|
|
3130
|
+
workspaceId: 'workspace-local-active',
|
|
3131
|
+
setupState: {
|
|
3132
|
+
globalSetupState: 'present',
|
|
3133
|
+
workspaceSetupState: 'present',
|
|
3134
|
+
runtimeMode: 'local',
|
|
3135
|
+
workspaceId: 'workspace-local-active',
|
|
3136
|
+
mode: 'core',
|
|
3137
|
+
forceSetup: false,
|
|
3138
|
+
forceGlobalSetup: false,
|
|
3139
|
+
forceWorkspaceSetup: false
|
|
3140
|
+
}
|
|
3141
|
+
});
|
|
3142
|
+
}
|
|
3143
|
+
if (url.includes('/api/taskforce/tasks')) {
|
|
3144
|
+
return jsonResponse({ tasks: mockTasks });
|
|
3145
|
+
}
|
|
3146
|
+
if (url.includes('/api/taskforce/archive')) {
|
|
3147
|
+
return jsonResponse({ archived: [] });
|
|
3148
|
+
}
|
|
3149
|
+
if (url.includes('/api/taskforce/categories')) {
|
|
3150
|
+
return jsonResponse(mockCategories);
|
|
3151
|
+
}
|
|
3152
|
+
if (url.includes('/api/taskforce/types')) {
|
|
3153
|
+
return jsonResponse({ types: [] });
|
|
3154
|
+
}
|
|
3155
|
+
if (url.includes('/api/taskforce/priorities')) {
|
|
3156
|
+
return jsonResponse({ priorities: [] });
|
|
3157
|
+
}
|
|
3158
|
+
if (url.includes('/api/taskforce/approaches')) {
|
|
3159
|
+
return jsonResponse({ approaches: [] });
|
|
3160
|
+
}
|
|
3161
|
+
if (url.includes('/api/taskforce/specialists')) {
|
|
3162
|
+
return jsonResponse({ specialists: [] });
|
|
3163
|
+
}
|
|
3164
|
+
if (url.includes('/api/taskforce/workflow-templates')) {
|
|
3165
|
+
return jsonResponse({ templates: [] });
|
|
3166
|
+
}
|
|
3167
|
+
if (url.includes('/api/taskforce/initiatives')) {
|
|
3168
|
+
return jsonResponse([]);
|
|
3169
|
+
}
|
|
3170
|
+
if (url.includes('/api/taskforce/workstreams')) {
|
|
3171
|
+
return jsonResponse([]);
|
|
3172
|
+
}
|
|
3173
|
+
return jsonResponse({});
|
|
3174
|
+
});
|
|
3175
|
+
vi.stubGlobal('fetch', fetchMock);
|
|
3176
|
+
render(_jsx(TaskforceCore, { config: { cloudAuthBaseUrl: 'https://cloud.example.com' } }));
|
|
3177
|
+
await waitFor(() => {
|
|
3178
|
+
expect(screen.getByTitle('Open tasks matching current filters')).toHaveTextContent('1/1');
|
|
3179
|
+
expect(screen.getByTitle(/Account/i)).toBeInTheDocument();
|
|
3180
|
+
});
|
|
3181
|
+
await user.click(screen.getByTitle(/Account/i));
|
|
3182
|
+
await user.click(await screen.findByRole('menuitem', { name: 'Sign Out' }));
|
|
3183
|
+
await waitFor(() => {
|
|
3184
|
+
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('https://cloud.example.com/api/taskforce/auth/logout'))).toBe(true);
|
|
3185
|
+
expect(screen.getByTitle('Open tasks matching current filters')).toHaveTextContent('1/1');
|
|
3186
|
+
});
|
|
3187
|
+
await user.click(screen.getByTitle(/Account/i));
|
|
3188
|
+
expect(await screen.findByText('Sign In / Register')).toBeInTheDocument();
|
|
3189
|
+
});
|
|
3207
3190
|
it('does not pull from cloud when workspace sync toggle is disabled', async () => {
|
|
3208
3191
|
const fetchMock = vi.fn((url) => {
|
|
3209
3192
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
@@ -4745,13 +4728,14 @@ describe('TaskforceCore Integration', () => {
|
|
|
4745
4728
|
expect(screen.getByTitle('Workspace ID: workspace-cloud-active')).toBeInTheDocument();
|
|
4746
4729
|
});
|
|
4747
4730
|
});
|
|
4748
|
-
it('opens edit profile from the account menu and saves the updated display name', async () => {
|
|
4731
|
+
it('opens edit profile from the account menu and saves the updated display name and avatar', async () => {
|
|
4749
4732
|
const user = userEvent.setup();
|
|
4750
4733
|
let savedDisplayName = 'Original Name';
|
|
4734
|
+
let savedAvatarUrl = '';
|
|
4751
4735
|
const fetchMock = vi.fn((input, init) => {
|
|
4752
4736
|
const url = String(input);
|
|
4753
4737
|
if (url.includes('/api/taskforce/auth/runtime-config')) {
|
|
4754
|
-
return jsonResponse({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: '
|
|
4738
|
+
return jsonResponse({ config: { runtimeMode: 'local', workspaceMode: 'single-local', authSource: 'cloud' } });
|
|
4755
4739
|
}
|
|
4756
4740
|
if (url.includes('/api/taskforce/auth/session')) {
|
|
4757
4741
|
return jsonResponse({
|
|
@@ -4761,6 +4745,7 @@ describe('TaskforceCore Integration', () => {
|
|
|
4761
4745
|
userId: 'user-1',
|
|
4762
4746
|
email: 'user@example.com',
|
|
4763
4747
|
displayName: savedDisplayName,
|
|
4748
|
+
avatarUrl: savedAvatarUrl,
|
|
4764
4749
|
workspaceId: 'workspace-local-active'
|
|
4765
4750
|
});
|
|
4766
4751
|
}
|
|
@@ -4817,15 +4802,38 @@ describe('TaskforceCore Integration', () => {
|
|
|
4817
4802
|
return jsonResponse({ templates: [] });
|
|
4818
4803
|
if (url.includes('/api/taskforce/workspace/assignee-options'))
|
|
4819
4804
|
return jsonResponse({ options: [] });
|
|
4805
|
+
if (url.includes('/api/taskforce/auth/profile/avatar/init')) {
|
|
4806
|
+
return jsonResponse({
|
|
4807
|
+
success: true,
|
|
4808
|
+
draftId: 'avatar-draft-1',
|
|
4809
|
+
uploadUrl: 'https://upload.example.com/avatar-draft-1',
|
|
4810
|
+
relativePath: 'users/user-1/profile/avatar/avatar-draft-1-avatar.png',
|
|
4811
|
+
path: '/api/taskforce/context/users%2Fuser-1%2Fprofile%2Favatar%2Favatar-draft-1-avatar.png'
|
|
4812
|
+
});
|
|
4813
|
+
}
|
|
4814
|
+
if (url.includes('https://upload.example.com/avatar-draft-1')) {
|
|
4815
|
+
return Promise.resolve({ ok: true, status: 200, json: async () => ({}) });
|
|
4816
|
+
}
|
|
4817
|
+
if (url.includes('/api/taskforce/auth/profile/avatar/finalize')) {
|
|
4818
|
+
return jsonResponse({
|
|
4819
|
+
success: true,
|
|
4820
|
+
draftId: 'avatar-draft-1',
|
|
4821
|
+
avatarUrl: '/api/taskforce/context/users%2Fuser-1%2Fprofile%2Favatar%2Favatar-draft-1-avatar.png'
|
|
4822
|
+
});
|
|
4823
|
+
}
|
|
4820
4824
|
if (url.includes('/api/taskforce/auth/profile')) {
|
|
4821
4825
|
const body = JSON.parse(String(init?.body || '{}'));
|
|
4822
4826
|
savedDisplayName = String(body?.displayName || '').trim() || savedDisplayName;
|
|
4827
|
+
if (body?.avatarDraftId === 'avatar-draft-1') {
|
|
4828
|
+
savedAvatarUrl = '/api/taskforce/context/users%2Fuser-1%2Fprofile%2Favatar%2Favatar-draft-1-avatar.png';
|
|
4829
|
+
}
|
|
4823
4830
|
return jsonResponse({
|
|
4824
4831
|
success: true,
|
|
4825
4832
|
profile: {
|
|
4826
4833
|
userId: 'user-1',
|
|
4827
4834
|
email: 'user@example.com',
|
|
4828
|
-
displayName: savedDisplayName
|
|
4835
|
+
displayName: savedDisplayName,
|
|
4836
|
+
avatarUrl: savedAvatarUrl
|
|
4829
4837
|
}
|
|
4830
4838
|
});
|
|
4831
4839
|
}
|
|
@@ -4845,17 +4853,24 @@ describe('TaskforceCore Integration', () => {
|
|
|
4845
4853
|
const displayNameInput = within(modal).getByPlaceholderText('Display name');
|
|
4846
4854
|
await user.clear(displayNameInput);
|
|
4847
4855
|
await user.type(displayNameInput, 'Updated Name');
|
|
4856
|
+
const avatarInput = within(modal).getByLabelText('Upload profile photo');
|
|
4857
|
+
await user.upload(avatarInput, new File(['avatar'], 'avatar.png', { type: 'image/png' }));
|
|
4848
4858
|
await user.click(within(modal).getByRole('button', { name: 'Save Profile' }));
|
|
4849
4859
|
await waitFor(() => {
|
|
4850
|
-
const profileCall = fetchMock.mock.calls.find((call) =>
|
|
4860
|
+
const profileCall = fetchMock.mock.calls.find((call) => {
|
|
4861
|
+
const url = String(call[0]);
|
|
4862
|
+
return url.includes('/api/taskforce/auth/profile') && !url.includes('/api/taskforce/auth/profile/avatar/');
|
|
4863
|
+
});
|
|
4851
4864
|
expect(profileCall).toBeTruthy();
|
|
4852
4865
|
const payload = JSON.parse(String(profileCall?.[1]?.body || '{}'));
|
|
4853
4866
|
expect(payload.displayName).toBe('Updated Name');
|
|
4867
|
+
expect(payload.avatarDraftId).toBe('avatar-draft-1');
|
|
4854
4868
|
});
|
|
4855
4869
|
await waitFor(() => {
|
|
4856
4870
|
expect(screen.queryByText('Edit Profile')).not.toBeInTheDocument();
|
|
4857
4871
|
});
|
|
4858
4872
|
await user.click(screen.getByTitle(/Account/i));
|
|
4859
4873
|
expect(await screen.findByText('Updated Name')).toBeInTheDocument();
|
|
4874
|
+
expect(screen.getByTitle(/Account/i).querySelector('img')).toHaveAttribute('src', '/api/taskforce/context/users%2Fuser-1%2Fprofile%2Favatar%2Favatar-draft-1-avatar.png');
|
|
4860
4875
|
});
|
|
4861
4876
|
});
|