@taskforcehq/taskforce 0.3.330 → 0.3.332
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 +7 -2
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.d.ts +2 -2
- package/dist/components/features/agentCapabilities/AgentCapabilitiesPrototype.js +2 -2
- package/dist/components/features/planning/PlanningModule.js +15 -3
- package/dist/components/features/planning/planningMetadata.d.ts +10 -0
- package/dist/components/features/planning/planningMetadata.js +8 -0
- package/dist/components/views/StandaloneLayout.js +35 -1
- package/dist/components/views/panels/PlanningDrawer.d.ts +2 -1
- package/dist/components/views/panels/PlanningDrawer.js +12 -8
- package/dist/config/envSchema.js +7 -0
- package/dist/core/McpTokenService.d.ts +2 -0
- package/dist/core/McpTokenService.js +13 -5
- package/dist/core/Taskforce.d.ts +11 -3
- package/dist/core/Taskforce.js +33 -3
- package/dist/core/types.d.ts +1 -0
- package/dist/documentReviews/DocumentReviewCommentStore.d.ts +2 -0
- package/dist/documentReviews/DocumentReviewCommentStore.js +28 -9
- package/dist/mcp/httpProtocolRouting.d.ts +1 -0
- package/dist/mcp/httpProtocolRouting.js +8 -5
- package/dist/mcp/runtime.js +24 -5
- package/dist/mcp/taskPlanningRegistrar.js +2 -2
- package/dist/mcp/toolProfiles.js +1 -0
- package/dist/mcp/toolRegistry.js +8 -8
- package/dist/server/buildInfo.d.ts +1 -0
- package/dist/server/buildInfo.js +5 -1
- package/dist/server/cloudMcpCapacityTelemetry.d.ts +68 -0
- package/dist/server/cloudMcpCapacityTelemetry.js +221 -0
- package/dist/server/localWorkspaceSyncServerRuntime.js +13 -5
- package/dist/server/routes/admin.js +14 -2
- package/dist/server/routes/shared.d.ts +3 -0
- package/dist/server/routes/shared.js +280 -240
- package/dist/service/localServiceCli.d.ts +2 -2
- package/dist/service/localServiceCli.js +4 -3
- package/dist/services/executorPhaseAIssuerCore.d.ts +13 -0
- package/dist/services/executorPhaseAIssuerCore.js +107 -0
- package/dist/services/taskforceAgentMcpBridge.d.ts +1 -1
- package/dist/services/taskforceAgentMcpBridge.js +35 -5
- package/dist/shared/executorPhaseASchemaValidation.js +1 -1
- package/dist/storage/postgresAdapter.js +3 -1
- package/dist/storage/postgresRequestDiagnostics.d.ts +9 -1
- package/dist/storage/postgresRequestDiagnostics.js +15 -2
- package/dist/storage/postgresWorker.js +13 -3
- package/dist/storage/postgresWorkerProtocol.d.ts +6 -0
- package/dist/storage/workflowStore.d.ts +2 -0
- package/dist/storage/workflowStore.js +39 -2
- package/dist/sync/cloudSyncApi.js +4 -2
- package/dist/sync/collaborationSyncState.js +5 -0
- package/dist/sync/coordinator/localWorkspaceSyncRunner.d.ts +3 -1
- package/dist/sync/coordinator/localWorkspaceSyncRunner.js +18 -0
- package/dist/sync/engine/workspaceSyncV2ChangeDispatcher.js +2 -0
- package/dist/sync/syncApplyHandlers.d.ts +1 -0
- package/dist/sync/syncApplyHandlers.js +72 -15
- package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.d.ts +7 -7
- package/dist/sync/v3/durableWorkflowRuntimeMutationRouter.js +50 -13
- package/dist/sync/v3/localOutboxRecoveryPreparationService.js +8 -2
- package/dist/sync/v3/localTaskCreateOutboxService.d.ts +3 -1
- package/dist/sync/v3/localTaskCreateOutboxService.js +6 -1
- package/dist/sync/v3/localTaskDeleteOutboxService.js +1 -0
- package/dist/sync/v3/localTaskLifecycleOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskLifecycleOutboxService.js +5 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskMetadataOutboxService.js +6 -2
- package/dist/sync/v3/localTaskStatusOutboxService.d.ts +2 -1
- package/dist/sync/v3/localTaskStatusOutboxService.js +20 -5
- package/dist/sync/v3/syncDurabilityStore.d.ts +2 -1
- package/dist/sync/v3/syncDurabilityStore.js +34 -6
- package/dist/sync/v3/workflowRuntimeMutationService.d.ts +5 -0
- package/dist/sync/v3/workflowRuntimeMutationService.js +124 -71
- package/dist/sync/v3/workspaceSyncV3CombinedLocalApply.js +25 -3
- package/dist/sync/v3/workspaceSyncV3Feed.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3Feed.js +6 -1
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.d.ts +2 -0
- package/dist/sync/v3/workspaceSyncV3NestedTaskProjection.js +49 -18
- package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.d.ts +1 -0
- package/dist/sync/v3/workspaceSyncV3WorkflowRuntimeApply.js +50 -5
- package/dist/ui/assets/{AiIdentityRosterCard-OcSz1PkK.js → AiIdentityRosterCard-CDMPtnbC.js} +1 -1
- package/dist/ui/assets/{AiProfilesModule-ByhYW5Xi.js → AiProfilesModule-Cww85C4E.js} +1 -1
- package/dist/ui/assets/{AnnotatedAttachmentWorkspace-BTM6nIg_.js → AnnotatedAttachmentWorkspace-Dt9TKYSH.js} +1 -1
- package/dist/ui/assets/{AssetTraySortControl-DabEG8L4.js → AssetTraySortControl-CKgapnxu.js} +1 -1
- package/dist/ui/assets/{ContextAttachmentManager-Coa8yVGM.js → ContextAttachmentManager-T-cEVcOZ.js} +2 -2
- package/dist/ui/assets/{DocumentWorkspace-BB0qhdix.js → DocumentWorkspace-HD4MUjAZ.js} +1 -1
- package/dist/ui/assets/{EntityActivityTimeline-CZ8x4UJE.js → EntityActivityTimeline-_V0zfMuh.js} +1 -1
- package/dist/ui/assets/PlanningModule-mTkBygsB.js +1 -0
- package/dist/ui/assets/{PlansPage-D86pZSSl.js → PlansPage-Btiq06VO.js} +1 -1
- package/dist/ui/assets/{TaskContextUpload-BVIkU9yT.js → TaskContextUpload-C00NVUpn.js} +1 -1
- package/dist/ui/assets/{TaskSettings-Cv1mNhv_.js → TaskSettings-Cp-L-4xh.js} +1 -1
- package/dist/ui/assets/{TaskforceAgentsModule-gmH-PD2q.js → TaskforceAgentsModule-Coq9-PUp.js} +2 -2
- package/dist/ui/assets/{WorkflowManagerModule-DbfnHJ-f.js → WorkflowManagerModule-CuqRzMmM.js} +1 -1
- package/dist/ui/assets/index-DSQINwt9.js +7 -0
- package/dist/ui/index.html +1 -1
- package/package.json +3 -1
- package/dist/ui/assets/PlanningModule-pRPjR7v5.js +0 -1
- package/dist/ui/assets/index-CTsDBaef.js +0 -7
package/README.md
CHANGED
|
@@ -68,12 +68,17 @@ To confirm which workspace Taskforce is using:
|
|
|
68
68
|
taskforce status
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
To connect a local AI client through MCP,
|
|
71
|
+
To connect a local AI client through MCP, run the MCP entrypoint from the same workspace:
|
|
72
72
|
|
|
73
73
|
```bash
|
|
74
74
|
taskforce mcp
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
The command is a thin stdio adapter for the shared local Taskforce service. Multiple
|
|
78
|
+
local AI clients and `taskforce open` reuse one service process, which remains the sole
|
|
79
|
+
owner of the workspace SQLite database and local sync executor. Client configuration
|
|
80
|
+
does not require a streaming option or a cloud token.
|
|
81
|
+
|
|
77
82
|
## What Taskforce Includes
|
|
78
83
|
|
|
79
84
|
### Task Management
|
|
@@ -115,7 +120,7 @@ taskforce mcp
|
|
|
115
120
|
|
|
116
121
|
Taskforce includes built-in MCP support so AI clients can work directly with your workspace.
|
|
117
122
|
|
|
118
|
-
Local MCP is for AI clients running on the same machine as your workspace. It does not require a cloud token.
|
|
123
|
+
Local MCP is for AI clients running on the same machine as your workspace. It does not require a cloud token. Generated configurations use a shared-proxy stdio entrypoint; the Global CLI configuration launches `taskforce mcp`, while Local / npx configurations launch the corresponding packaged or source entrypoint. Each discovers or starts the same shared local service and forwards stdio MCP messages to it. Use `taskforce service status <project>` to inspect that service and `taskforce service stop <project> --drain` for a routine administrative stop after active clients disconnect.
|
|
119
124
|
|
|
120
125
|
Cloud MCP is for hosted remote clients and uses token-based access.
|
|
121
126
|
|
|
@@ -10,8 +10,8 @@ export declare const TASKFORCE_TOOL_MODULES: readonly [{
|
|
|
10
10
|
readonly icon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
|
|
11
11
|
}, {
|
|
12
12
|
readonly key: "documents";
|
|
13
|
-
readonly label: "Documents";
|
|
14
|
-
readonly description: "Find and read Taskforce documents and
|
|
13
|
+
readonly label: "Documents and reviews";
|
|
14
|
+
readonly description: "Find and read Taskforce documents, inspect review context, and post document review feedback.";
|
|
15
15
|
readonly icon: import("react").ForwardRefExoticComponent<Omit<import("lucide-react").LucideProps, "ref"> & import("react").RefAttributes<SVGSVGElement>>;
|
|
16
16
|
}, {
|
|
17
17
|
readonly key: "images";
|
|
@@ -17,8 +17,8 @@ export const TASKFORCE_TOOL_MODULES = [
|
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
key: 'documents',
|
|
20
|
-
label: 'Documents',
|
|
21
|
-
description: 'Find and read Taskforce documents and
|
|
20
|
+
label: 'Documents and reviews',
|
|
21
|
+
description: 'Find and read Taskforce documents, inspect review context, and post document review feedback.',
|
|
22
22
|
icon: FileText,
|
|
23
23
|
},
|
|
24
24
|
{
|
|
@@ -11,6 +11,7 @@ import { PlanningOwnerDropdown, normalizePlanningOwnerOptions, } from './Plannin
|
|
|
11
11
|
import { buildPlanningNavigatorResults, PLANNING_NAVIGATOR_SORT_OPTIONS, } from './planningNavigator';
|
|
12
12
|
import { PlanningEntitySummary, PlanningReferenceBadge, } from './PlanningEntitySummary';
|
|
13
13
|
import { PlanningVisualIdentity } from './PlanningVisualIdentity';
|
|
14
|
+
import { hasPlanningMetadataChanges } from './planningMetadata';
|
|
14
15
|
import { PlanningTaskCard } from './PlanningTaskCard';
|
|
15
16
|
import { PlanningTaskArrangeControl } from './PlanningTaskArrangeControl';
|
|
16
17
|
import planningDropdownStyles from './PlanningDropdownControl.module.css';
|
|
@@ -21,7 +22,7 @@ import { PlanningUnlinkConfirmation, } from './PlanningUnlinkConfirmation';
|
|
|
21
22
|
import { PlanningLinkPicker } from './PlanningLinkPicker';
|
|
22
23
|
import { PlanningEntityActivity, PlanningEntityContext, PlanningEntityDescription, } from './PlanningEntityDetails';
|
|
23
24
|
import styles from './PlanningModule.module.css';
|
|
24
|
-
function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialDescription = '', ownerOptions, fixedInitiativeId, formId, showActions = true, onSavingChange, onCancel, onSave, }) {
|
|
25
|
+
function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialDescription = '', ownerOptions, fixedInitiativeId, formId, showActions = true, onSavingChange, onDirtyChange, onCancel, onSave, }) {
|
|
25
26
|
const label = entityType === 'initiative' ? 'Initiative' : 'Workstream';
|
|
26
27
|
const titleInputId = React.useId();
|
|
27
28
|
const titleErrorId = React.useId();
|
|
@@ -35,6 +36,10 @@ function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialD
|
|
|
35
36
|
const [titleError, setTitleError] = React.useState(null);
|
|
36
37
|
const [saveError, setSaveError] = React.useState(null);
|
|
37
38
|
const [isSaving, setIsSaving] = React.useState(false);
|
|
39
|
+
const isDirty = mode === 'create' || hasPlanningMetadataChanges({ title: initialTitle, description: initialDescription }, { title, description });
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
onDirtyChange?.(isDirty);
|
|
42
|
+
}, [isDirty, onDirtyChange]);
|
|
38
43
|
React.useEffect(() => {
|
|
39
44
|
titleInputRef.current?.focus();
|
|
40
45
|
}, []);
|
|
@@ -48,6 +53,8 @@ function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialD
|
|
|
48
53
|
titleInputRef.current?.focus();
|
|
49
54
|
return;
|
|
50
55
|
}
|
|
56
|
+
if (!isDirty)
|
|
57
|
+
return;
|
|
51
58
|
setTitleError(null);
|
|
52
59
|
setSaveError(null);
|
|
53
60
|
setIsSaving(true);
|
|
@@ -87,7 +94,7 @@ function PlanningWorkspaceEditor({ entityType, mode, initialTitle = '', initialD
|
|
|
87
94
|
setTitleError(null);
|
|
88
95
|
}, placeholder: entityType === 'initiative' ? 'Enter initiative title' : 'Enter workstream title' }), titleError ? _jsx("p", { id: titleErrorId, className: "tf-text-error", children: titleError }) : null] }), _jsxs("div", { className: styles.workspaceEditorField, children: [_jsxs("label", { htmlFor: descriptionInputId, children: [label, " description"] }), _jsx("textarea", { id: descriptionInputId, value: description, disabled: isSaving, rows: 5, onChange: (event) => setDescription(event.target.value), placeholder: entityType === 'initiative'
|
|
89
96
|
? 'Describe the broader outcome this initiative is meant to achieve.'
|
|
90
|
-
: 'Describe the lane of work this workstream will coordinate.' })] }), mode === 'create' && ownerOptions ? (_jsxs("div", { className: styles.workspaceEditorField, children: [_jsxs("span", { children: [label, " owner"] }), _jsx(PlanningOwnerDropdown, { value: ownerId, options: ownerOptions, onChange: setOwnerId, disabled: isSaving, ariaLabel: `${label} owner`, className: `${planningDropdownStyles.control} ${styles.selectedEntityOwnerDropdown}` })] })) : null, saveError ? _jsx("p", { className: "tf-text-error", role: "alert", children: saveError }) : null, showActions ? _jsxs("div", { className: styles.workspaceEditorActions, children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: onCancel, disabled: isSaving, children: "Cancel" }), _jsxs("button", { type: "submit", className: "tf-button-primary tf-button-compact", disabled: isSaving, children: [isSaving ? _jsx(Loader2, { size: 14, className: styles.spinner, "aria-hidden": "true" }) : null, mode === 'create' ? `Create ${label}` : 'Save'] })] }) : null] }));
|
|
97
|
+
: 'Describe the lane of work this workstream will coordinate.' })] }), mode === 'create' && ownerOptions ? (_jsxs("div", { className: styles.workspaceEditorField, children: [_jsxs("span", { children: [label, " owner"] }), _jsx(PlanningOwnerDropdown, { value: ownerId, options: ownerOptions, onChange: setOwnerId, disabled: isSaving, ariaLabel: `${label} owner`, className: `${planningDropdownStyles.control} ${styles.selectedEntityOwnerDropdown}` })] })) : null, saveError ? _jsx("p", { className: "tf-text-error", role: "alert", children: saveError }) : null, showActions ? _jsxs("div", { className: styles.workspaceEditorActions, children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: onCancel, disabled: isSaving, children: "Cancel" }), _jsxs("button", { type: "submit", className: "tf-button-primary tf-button-compact", disabled: isSaving || !isDirty, children: [isSaving ? _jsx(Loader2, { size: 14, className: styles.spinner, "aria-hidden": "true" }) : null, mode === 'create' ? `Create ${label}` : 'Save'] })] }) : null] }));
|
|
91
98
|
}
|
|
92
99
|
function InitiativeButton({ initiative, ownerOption, ownerOptions, workstreamCount, selected, onSelect, onChangeOwner, onChangeIdentity, }) {
|
|
93
100
|
return (_jsxs("div", { className: `${styles.entityCard} ${initiative.isArchived ? styles.entityCardArchived : ''} ${selected ? styles.entityCardSelected : ''}`.trim(), children: [_jsx(PlanningReferenceBadge, { label: formatInitiativeReference(initiative), entityType: "initiative", entityName: initiative.title, interactive: false }), _jsx("div", { className: styles.entityCardBody, children: _jsx("div", { role: "button", tabIndex: 0, className: styles.entityCardContentButton, onClick: onSelect, onKeyDown: (event) => {
|
|
@@ -115,6 +122,7 @@ function SelectedEntityContext({ item, entityType, ownerOptions, onChangeOwner,
|
|
|
115
122
|
const [isSavingOwner, setIsSavingOwner] = React.useState(false);
|
|
116
123
|
const [isEditing, setIsEditing] = React.useState(false);
|
|
117
124
|
const [isEditingSaving, setIsEditingSaving] = React.useState(false);
|
|
125
|
+
const [isEditingDirty, setIsEditingDirty] = React.useState(false);
|
|
118
126
|
const [archiveConfirmationVisible, setArchiveConfirmationVisible] = React.useState(false);
|
|
119
127
|
const [archiveBusy, setArchiveBusy] = React.useState(false);
|
|
120
128
|
const editFormId = React.useId();
|
|
@@ -140,6 +148,7 @@ function SelectedEntityContext({ item, entityType, ownerOptions, onChangeOwner,
|
|
|
140
148
|
React.useEffect(() => {
|
|
141
149
|
setIsEditing(false);
|
|
142
150
|
setIsEditingSaving(false);
|
|
151
|
+
setIsEditingDirty(false);
|
|
143
152
|
setArchiveConfirmationVisible(false);
|
|
144
153
|
setArchiveBusy(false);
|
|
145
154
|
}, [item.id]);
|
|
@@ -199,7 +208,10 @@ function SelectedEntityContext({ item, entityType, ownerOptions, onChangeOwner,
|
|
|
199
208
|
? archiveConfirmationVisible
|
|
200
209
|
: undefined, children: archiveState.mode === 'archived'
|
|
201
210
|
? _jsx(RotateCcw, { size: 14, "aria-hidden": "true" })
|
|
202
|
-
: _jsx(Archive, { size: 14, "aria-hidden": "true" }) })) : null, !isEditing && !item.isArchived && onUpdate ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () =>
|
|
211
|
+
: _jsx(Archive, { size: 14, "aria-hidden": "true" }) })) : null, !isEditing && !item.isArchived && onUpdate ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: () => {
|
|
212
|
+
setIsEditingDirty(false);
|
|
213
|
+
setIsEditing(true);
|
|
214
|
+
}, "aria-label": `Edit ${label.toLowerCase()}`, title: `Edit ${label.toLowerCase()}`, children: _jsx(Pencil, { size: 14, "aria-hidden": "true" }) })) : null, isEditing ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-button-secondary tf-button-compact", onClick: () => setIsEditing(false), disabled: isEditingSaving, children: "Cancel" }), _jsx("button", { type: "submit", form: editFormId, className: "tf-button-primary tf-button-compact", disabled: isEditingSaving || !isEditingDirty, children: "Save" })] })) : null] })] }), archiveConfirmationVisible && archiveState.mode === 'ready' ? (_jsx(PlanningArchiveConfirmation, { entityType: entityType, title: item.title, activeTaskCount: item.archiveActiveTaskCount, activeWorkstreamCount: item.archiveActiveWorkstreamCount, busy: archiveBusy, onCancel: () => setArchiveConfirmationVisible(false), onConfirm: () => void handleConfirmArchive() })) : null, relationshipContent, isEditing && onUpdate ? (_jsx(PlanningWorkspaceEditor, { entityType: entityType, mode: "edit", formId: editFormId, showActions: false, onSavingChange: setIsEditingSaving, onDirtyChange: setIsEditingDirty, initialTitle: item.title, initialDescription: item.description || '', onCancel: () => setIsEditing(false), onSave: async (input) => {
|
|
203
215
|
await onUpdate(input);
|
|
204
216
|
setIsEditing(false);
|
|
205
217
|
} }, `${entityType}:${item.id}`)) : (_jsxs(_Fragment, { children: [_jsx("h3", { className: "tf-heading-card", children: item.title }), _jsx(PlanningEntityDescription, { description: item.description, label: label, taskReferences: taskReferences, className: `${styles.selectedEntityDescription} tf-scrollbar`.trim(), markdownClassName: styles.selectedEntityDescriptionMarkdown })] })), _jsxs("div", { className: styles.selectedEntityOwnerField, children: [_jsx("span", { children: "Owner" }), _jsx(PlanningOwnerDropdown, { value: ownerValue, options: ownerOptions, onChange: (nextOwnerId) => void handleOwnerChange(nextOwnerId), disabled: !onChangeOwner || Boolean(item.isArchived) || isSavingOwner, ariaLabel: `${label} owner`, className: `${planningDropdownStyles.control} ${styles.selectedEntityOwnerDropdown}` })] }), _jsx("div", { className: styles.selectedEntityDetailsSection, children: _jsx(PlanningEntityContext, { entityType: entityType, entityId: item.id, referenceLabel: entityType === 'initiative'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type PlanningMetadataDraft = {
|
|
2
|
+
title: string;
|
|
3
|
+
description?: string | null;
|
|
4
|
+
};
|
|
5
|
+
export declare function hasPlanningMetadataChanges(current: PlanningMetadataDraft, draft: PlanningMetadataDraft): boolean;
|
|
6
|
+
export declare function hasWorkstreamMetadataChanges(current: PlanningMetadataDraft & {
|
|
7
|
+
initiativeId?: string | null;
|
|
8
|
+
}, draft: PlanningMetadataDraft & {
|
|
9
|
+
initiativeId?: string | null;
|
|
10
|
+
}): boolean;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function hasPlanningMetadataChanges(current, draft) {
|
|
2
|
+
return draft.title.trim() !== current.title.trim()
|
|
3
|
+
|| (draft.description?.trim() || null) !== (current.description?.trim() || null);
|
|
4
|
+
}
|
|
5
|
+
export function hasWorkstreamMetadataChanges(current, draft) {
|
|
6
|
+
return hasPlanningMetadataChanges(current, draft)
|
|
7
|
+
|| (draft.initiativeId || null) !== (current.initiativeId || null);
|
|
8
|
+
}
|
|
@@ -64,6 +64,7 @@ import { FilterToolbar } from './panels/FilterToolbar';
|
|
|
64
64
|
import { PlanningDrawer, getPlanningDrawerWidthPx, } from './panels/PlanningDrawer';
|
|
65
65
|
import { normalizePlanningTaskArrangeMode, } from '../features/planning/planningTaskArrangement';
|
|
66
66
|
import { resolveWorkstreamCreateInitiativeId } from '../features/planning/planningCreateIntent';
|
|
67
|
+
import { hasPlanningMetadataChanges, hasWorkstreamMetadataChanges, } from '../features/planning/planningMetadata';
|
|
67
68
|
import { shouldUsePlanningBootstrapTaskSummaries } from '../features/planning/planningWorkspaceModel';
|
|
68
69
|
import { getWorkspaceChromeDefinition, getWorkspaceModuleDefinitions, resolveWorkspaceModule, } from './chrome/workspaceChrome';
|
|
69
70
|
import { WorkspaceHeaderActions, WorkspaceHeaderDivider, WorkspaceHeaderSearch, WorkspaceHeaderSelect, WorkspacePrimaryAction, } from './chrome/WorkspaceChromeSections';
|
|
@@ -3087,6 +3088,36 @@ export function StandaloneLayout(props) {
|
|
|
3087
3088
|
setPlanningDraftColor(null);
|
|
3088
3089
|
setPlanningDraftInitiativeId('');
|
|
3089
3090
|
}, []);
|
|
3091
|
+
const planningEditorHasChanges = useMemo(() => {
|
|
3092
|
+
const activeEditor = planningSecondaryEditor || planningEditor;
|
|
3093
|
+
if (!activeEditor || activeEditor.mode === 'create')
|
|
3094
|
+
return true;
|
|
3095
|
+
const draft = {
|
|
3096
|
+
title: planningDraftTitle,
|
|
3097
|
+
description: planningDraftDescription,
|
|
3098
|
+
};
|
|
3099
|
+
if (activeEditor.entityType === 'initiative') {
|
|
3100
|
+
const current = planningStructure.allInitiativeById.get(activeEditor.targetId);
|
|
3101
|
+
return !current || hasPlanningMetadataChanges(current, draft);
|
|
3102
|
+
}
|
|
3103
|
+
const current = planningStructure.allWorkstreamById.get(activeEditor.targetId);
|
|
3104
|
+
if (!current)
|
|
3105
|
+
return true;
|
|
3106
|
+
const resolvedInitiative = resolveWorkstreamCreateInitiativeId(planningStructure.allInitiatives, null, planningDraftInitiativeId);
|
|
3107
|
+
return resolvedInitiative.invalid || hasWorkstreamMetadataChanges(current, {
|
|
3108
|
+
...draft,
|
|
3109
|
+
initiativeId: resolvedInitiative.initiativeId,
|
|
3110
|
+
});
|
|
3111
|
+
}, [
|
|
3112
|
+
planningDraftDescription,
|
|
3113
|
+
planningDraftInitiativeId,
|
|
3114
|
+
planningDraftTitle,
|
|
3115
|
+
planningEditor,
|
|
3116
|
+
planningSecondaryEditor,
|
|
3117
|
+
planningStructure.allInitiativeById,
|
|
3118
|
+
planningStructure.allInitiatives,
|
|
3119
|
+
planningStructure.allWorkstreamById,
|
|
3120
|
+
]);
|
|
3090
3121
|
const submitPlanningEditor = useCallback(async () => {
|
|
3091
3122
|
const activeEditor = planningSecondaryEditor || planningEditor;
|
|
3092
3123
|
if (!activeEditor || planningEditorSubmitInFlightRef.current)
|
|
@@ -3094,6 +3125,8 @@ export function StandaloneLayout(props) {
|
|
|
3094
3125
|
const title = planningDraftTitle.trim();
|
|
3095
3126
|
if (!title)
|
|
3096
3127
|
return;
|
|
3128
|
+
if (activeEditor.mode === 'edit' && !planningEditorHasChanges)
|
|
3129
|
+
return;
|
|
3097
3130
|
planningEditorSubmitInFlightRef.current = true;
|
|
3098
3131
|
setIsSubmittingPlanningEditor(true);
|
|
3099
3132
|
const isSecondaryEditor = Boolean(planningSecondaryEditor);
|
|
@@ -3188,6 +3221,7 @@ export function StandaloneLayout(props) {
|
|
|
3188
3221
|
planningDraftOwner,
|
|
3189
3222
|
planningDraftTitle,
|
|
3190
3223
|
planningEditor,
|
|
3224
|
+
planningEditorHasChanges,
|
|
3191
3225
|
planningSecondaryEditor,
|
|
3192
3226
|
planningStructure.allInitiatives,
|
|
3193
3227
|
props,
|
|
@@ -4230,7 +4264,7 @@ export function StandaloneLayout(props) {
|
|
|
4230
4264
|
setIsSubmittingPlanningEditor(false);
|
|
4231
4265
|
setPlanningSecondaryEditor(null);
|
|
4232
4266
|
setPlanningNestedWorkstreamDetailId(null);
|
|
4233
|
-
}, treeCollapsed: planningTreeCollapsed, primaryCollapsed: planningPrimaryCollapsed, secondaryCollapsed: planningSecondaryCollapsed, onCancelEditor: closePlanningEditor, onCancelSecondaryEditor: closePlanningSecondaryEditor, onSubmitEditor: submitPlanningEditor, isSubmittingEditor: isSubmittingPlanningEditor, onCreateInitiative: () => openPlanningCreateEditor('initiative'), onCreateWorkstream: () => openPlanningCreateEditor('workstream'), onCreateTaskInWorkstream: openAddTaskModalForWorkstream, onCreateWorkstreamInInitiative: (initiativeId) => openPlanningCreateEditor('workstream', initiativeId), onAssignInitiativeToWorkstream: handleAssignInitiativeToWorkstream, onOpenTaskById: planningWorkspaceModel.openTask, onSetTaskStatus: handleSetPlanningTaskStatus, onChangeTaskAssignee: handleChangePlanningTaskAssignee, onArchivePlanningTask: handleArchivePlanningTask, onArchiveResolvedWorkstream: handleArchiveResolvedWorkstream, onArchiveResolvedInitiative: handleArchiveResolvedInitiative, taskReferences: taskReferences, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAttachTaskToWorkstreamByReference: handleAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference: handleAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile: handleAddPlanningContextFile, onRemovePlanningContextFile: handleRemovePlanningContextFile, onUpdatePlanningContextCaption: handleUpdatePlanningContextCaption, onToggleInitiative: togglePlanningInitiative, onSelectInitiative: selectPlanningInitiative, onSelectWorkstream: selectPlanningWorkstream, onOpenInitiativeDetails: openInitiativeDetails, onOpenWorkstreamDetails: openWorkstreamDetails, onOpenNestedWorkstreamDetails: openNestedWorkstreamDetails, onEditInitiative: (initiativeId) => openPlanningEditDialog('initiative', initiativeId), onEditWorkstream: (workstreamId) => openPlanningEditDialog('workstream', workstreamId), onChangePlanningOwner: handleChangePlanningOwner, onChangePlanningIdentity: handleChangePlanningIdentity, onArchiveInitiative: (initiativeId) => {
|
|
4267
|
+
}, treeCollapsed: planningTreeCollapsed, primaryCollapsed: planningPrimaryCollapsed, secondaryCollapsed: planningSecondaryCollapsed, onCancelEditor: closePlanningEditor, onCancelSecondaryEditor: closePlanningSecondaryEditor, onSubmitEditor: submitPlanningEditor, isSubmittingEditor: isSubmittingPlanningEditor, isEditorSubmitDisabled: Boolean((planningSecondaryEditor || planningEditor)?.mode === 'edit') && !planningEditorHasChanges, onCreateInitiative: () => openPlanningCreateEditor('initiative'), onCreateWorkstream: () => openPlanningCreateEditor('workstream'), onCreateTaskInWorkstream: openAddTaskModalForWorkstream, onCreateWorkstreamInInitiative: (initiativeId) => openPlanningCreateEditor('workstream', initiativeId), onAssignInitiativeToWorkstream: handleAssignInitiativeToWorkstream, onOpenTaskById: planningWorkspaceModel.openTask, onSetTaskStatus: handleSetPlanningTaskStatus, onChangeTaskAssignee: handleChangePlanningTaskAssignee, onArchivePlanningTask: handleArchivePlanningTask, onArchiveResolvedWorkstream: handleArchiveResolvedWorkstream, onArchiveResolvedInitiative: handleArchiveResolvedInitiative, taskReferences: taskReferences, onAssignTaskToWorkstream: handleAssignTaskToWorkstream, onAttachTaskToWorkstreamByReference: handleAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference: handleAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile: handleAddPlanningContextFile, onRemovePlanningContextFile: handleRemovePlanningContextFile, onUpdatePlanningContextCaption: handleUpdatePlanningContextCaption, onToggleInitiative: togglePlanningInitiative, onSelectInitiative: selectPlanningInitiative, onSelectWorkstream: selectPlanningWorkstream, onOpenInitiativeDetails: openInitiativeDetails, onOpenWorkstreamDetails: openWorkstreamDetails, onOpenNestedWorkstreamDetails: openNestedWorkstreamDetails, onEditInitiative: (initiativeId) => openPlanningEditDialog('initiative', initiativeId), onEditWorkstream: (workstreamId) => openPlanningEditDialog('workstream', workstreamId), onChangePlanningOwner: handleChangePlanningOwner, onChangePlanningIdentity: handleChangePlanningIdentity, onArchiveInitiative: (initiativeId) => {
|
|
4234
4268
|
void props.archiveInitiative(initiativeId).then(() => {
|
|
4235
4269
|
pushNotice('Initiative archived', 'success');
|
|
4236
4270
|
}).catch((error) => {
|
|
@@ -116,6 +116,7 @@ type PlanningDrawerProps = {
|
|
|
116
116
|
onCancelSecondaryEditor?: () => void;
|
|
117
117
|
onSubmitEditor: () => void;
|
|
118
118
|
isSubmittingEditor: boolean;
|
|
119
|
+
isEditorSubmitDisabled?: boolean;
|
|
119
120
|
onCreateInitiative: () => void;
|
|
120
121
|
onCreateWorkstream: () => void;
|
|
121
122
|
onToggleInitiative: (initiativeId: string) => void;
|
|
@@ -149,4 +150,4 @@ type PlanningDrawerProps = {
|
|
|
149
150
|
onRemovePlanningContextFile?: (entityType: 'initiative' | 'workstream', entityId: string, index: number) => void;
|
|
150
151
|
onUpdatePlanningContextCaption?: (entityType: 'initiative' | 'workstream', entityId: string, index: number, caption: string) => void;
|
|
151
152
|
};
|
|
152
|
-
export declare function PlanningDrawer({ open, leftOffset, returnFocusRef, onWidthChange, initiatives, standaloneWorkstreams, planningLoadState, onRetryPlanning, taskScope, activeInitiativeId, activeWorkstreamId, navigatorSort, navigatorSortReady, onNavigatorSortChange, taskArrangeMode, onTaskArrangeModeChange, onReorderWorkstreamTasks, planningFavorites, onTogglePlanningFavorite, favoritesSectionCollapsed, initiativesSectionCollapsed, workstreamsSectionCollapsed, onToggleFavoritesSection, onToggleInitiativesSection, onToggleWorkstreamsSection, expandedInitiativeIds, detail, editor, secondaryEditor, secondaryPane, primaryHydrationState, secondaryHydrationState, onRetryPrimaryHydration, onRetrySecondaryHydration, currentWorkspaceId, currentOwnerId, theme, assigneeOptions, linkOptions, showTaskCardStatusLabel, draftInitiativeSummary, draftTitle, draftDescription, draftOwner, draftInitiativeId, draftIcon, draftColor, onChangeDraftTitle, onChangeDraftDescription, onChangeDraftOwner, onChangeDraftInitiativeId, onChangeDraftIcon, onChangeDraftColor, onCollapseTreePane, onExpandTreePane, onCollapsePrimaryPane, onExpandPrimaryPane, onCollapseSecondaryPane, onExpandSecondaryPane, onBackFromSecondary, treeCollapsed, primaryCollapsed, secondaryCollapsed, onCancelEditor, onCancelSecondaryEditor, onSubmitEditor, isSubmittingEditor, onCreateInitiative, onCreateWorkstream, onToggleInitiative, onSelectInitiative, onSelectWorkstream, onOpenInitiativeDetails, onOpenWorkstreamDetails, onOpenNestedWorkstreamDetails, onEditInitiative, onEditWorkstream, onChangePlanningOwner, onChangePlanningIdentity, onChangeTaskAssignee, onArchiveInitiative, onUnarchiveInitiative, onArchiveWorkstream, onUnarchiveWorkstream, onArchivePlanningTask, onArchiveResolvedWorkstream, onArchiveResolvedInitiative, onCreateTaskInWorkstream, onCreateWorkstreamInInitiative, onAssignInitiativeToWorkstream, onOpenTaskById, onSetTaskStatus, taskReferences, onAssignTaskToWorkstream, onAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile, onRemovePlanningContextFile, onUpdatePlanningContextCaption, }: PlanningDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
153
|
+
export declare function PlanningDrawer({ open, leftOffset, returnFocusRef, onWidthChange, initiatives, standaloneWorkstreams, planningLoadState, onRetryPlanning, taskScope, activeInitiativeId, activeWorkstreamId, navigatorSort, navigatorSortReady, onNavigatorSortChange, taskArrangeMode, onTaskArrangeModeChange, onReorderWorkstreamTasks, planningFavorites, onTogglePlanningFavorite, favoritesSectionCollapsed, initiativesSectionCollapsed, workstreamsSectionCollapsed, onToggleFavoritesSection, onToggleInitiativesSection, onToggleWorkstreamsSection, expandedInitiativeIds, detail, editor, secondaryEditor, secondaryPane, primaryHydrationState, secondaryHydrationState, onRetryPrimaryHydration, onRetrySecondaryHydration, currentWorkspaceId, currentOwnerId, theme, assigneeOptions, linkOptions, showTaskCardStatusLabel, draftInitiativeSummary, draftTitle, draftDescription, draftOwner, draftInitiativeId, draftIcon, draftColor, onChangeDraftTitle, onChangeDraftDescription, onChangeDraftOwner, onChangeDraftInitiativeId, onChangeDraftIcon, onChangeDraftColor, onCollapseTreePane, onExpandTreePane, onCollapsePrimaryPane, onExpandPrimaryPane, onCollapseSecondaryPane, onExpandSecondaryPane, onBackFromSecondary, treeCollapsed, primaryCollapsed, secondaryCollapsed, onCancelEditor, onCancelSecondaryEditor, onSubmitEditor, isSubmittingEditor, isEditorSubmitDisabled, onCreateInitiative, onCreateWorkstream, onToggleInitiative, onSelectInitiative, onSelectWorkstream, onOpenInitiativeDetails, onOpenWorkstreamDetails, onOpenNestedWorkstreamDetails, onEditInitiative, onEditWorkstream, onChangePlanningOwner, onChangePlanningIdentity, onChangeTaskAssignee, onArchiveInitiative, onUnarchiveInitiative, onArchiveWorkstream, onUnarchiveWorkstream, onArchivePlanningTask, onArchiveResolvedWorkstream, onArchiveResolvedInitiative, onCreateTaskInWorkstream, onCreateWorkstreamInInitiative, onAssignInitiativeToWorkstream, onOpenTaskById, onSetTaskStatus, taskReferences, onAssignTaskToWorkstream, onAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile, onRemovePlanningContextFile, onUpdatePlanningContextCaption, }: PlanningDrawerProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -758,7 +758,7 @@ function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor,
|
|
|
758
758
|
});
|
|
759
759
|
}, title: "Unlink workstream from initiative", ariaLabel: "Unlink workstream from initiative", children: _jsx(Unlink, { size: 14, "aria-hidden": "true" }) })) : null] })) : null })) : null, detail.type === 'workstream'
|
|
760
760
|
&& !parentInitiativeReferenceLabel
|
|
761
|
-
&& !inlineEditor ? (_jsx(PlanningLinkPicker, { options: linkOptions.initiatives, entityLabel: "initiative", onSelect: handleLinkInitiative, disabled: !onAssignInitiativeToWorkstream || isChangingRelationship, iconTrigger: true, triggerTitle: "Link initiative" })) : null, _jsx(PlanningReferenceBadge, { label: detailReferenceLabel || 'Reference pending', entityType: detail.type, entityName: detail.item.title, pending: !detailReferenceLabel })] }), _jsx("div", { className: panelStyles.detailTopActions, children: inlineEditor ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", onClick: inlineEditor.onCancel, disabled: inlineEditor.isSubmitting, children: "Cancel" }), _jsx("button", { type: "submit", form: inlineEditorFormId, className: "tf-button-primary tf-button-compact", disabled: inlineEditor.isSubmitting, children: inlineEditor.isSubmitting ? 'Saving…' : 'Save' })] })) : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-control-icon", disabled: archiveDetailState.mode === 'blocked' || archiveDetailState.mode === 'checking', onClick: () => {
|
|
761
|
+
&& !inlineEditor ? (_jsx(PlanningLinkPicker, { options: linkOptions.initiatives, entityLabel: "initiative", onSelect: handleLinkInitiative, disabled: !onAssignInitiativeToWorkstream || isChangingRelationship, iconTrigger: true, triggerTitle: "Link initiative" })) : null, _jsx(PlanningReferenceBadge, { label: detailReferenceLabel || 'Reference pending', entityType: detail.type, entityName: detail.item.title, pending: !detailReferenceLabel })] }), _jsx("div", { className: panelStyles.detailTopActions, children: inlineEditor ? (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", onClick: inlineEditor.onCancel, disabled: inlineEditor.isSubmitting, children: "Cancel" }), _jsx("button", { type: "submit", form: inlineEditorFormId, className: "tf-button-primary tf-button-compact", disabled: inlineEditor.isSubmitting || inlineEditor.isSubmitDisabled, children: inlineEditor.isSubmitting ? 'Saving…' : 'Save' })] })) : (_jsxs(_Fragment, { children: [_jsx("button", { type: "button", className: "tf-control-icon", disabled: archiveDetailState.mode === 'blocked' || archiveDetailState.mode === 'checking', onClick: () => {
|
|
762
762
|
if (archiveDetailState.mode === 'archived') {
|
|
763
763
|
onUnarchive?.();
|
|
764
764
|
}
|
|
@@ -772,7 +772,7 @@ function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor,
|
|
|
772
772
|
? archiveEntityConfirmationVisible
|
|
773
773
|
: undefined, children: archiveDetailState.mode === 'archived'
|
|
774
774
|
? _jsx(RotateCcw, { size: 14, "aria-hidden": "true" })
|
|
775
|
-
: _jsx(Archive, { size: 14, "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: onEdit, title: `Edit ${detailLabel.toLowerCase()} details`, "aria-label": `Edit ${detailLabel.toLowerCase()} details`, children: _jsx(Pencil, { size: 14, "aria-hidden": "true" }) })] })) })] }), archiveEntityConfirmationVisible && archiveDetailState.mode === 'ready' ? (_jsx(PlanningArchiveConfirmation, { entityType: detail.type, title: detail.item.title, activeTaskCount: detail.item.archiveActiveTaskCount, activeWorkstreamCount: detail.item.archiveActiveWorkstreamCount, busy: archiveEntityBusy, onCancel: () => setArchiveEntityConfirmationVisible(false), onConfirm: () => void handleConfirmArchiveEntity() })) : null, detail.type === 'workstream' ? renderRelationshipFeedback('workstream', detail.item.id) : null, inlineEditor ? (_jsx(PlanningDrawerEditor, { editor: inlineEditor.editor, draftTitle: inlineEditor.draftTitle, draftDescription: inlineEditor.draftDescription, draftOwner: inlineEditor.draftOwner, draftInitiativeId: inlineEditor.draftInitiativeId, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: inlineEditor.draftInitiativeSummary, onChangeDraftTitle: inlineEditor.onChangeDraftTitle, onChangeDraftDescription: inlineEditor.onChangeDraftDescription, onChangeDraftOwner: inlineEditor.onChangeDraftOwner, onChangeDraftInitiativeId: inlineEditor.onChangeDraftInitiativeId, onCancel: inlineEditor.onCancel, onSubmit: inlineEditor.onSubmit, isSubmitting: inlineEditor.isSubmitting, inline: true, formId: inlineEditorFormId })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: panelStyles.detailTitleRow, children: [_jsx(PlanningVisualIdentity, { entityType: detail.type, entityId: detail.item.id, icon: detail.item.icon, color: detail.item.color, size: "detail", disabled: Boolean(detail.item.isArchived), onChange: onChangePlanningIdentity
|
|
775
|
+
: _jsx(Archive, { size: 14, "aria-hidden": "true" }) }), _jsx("button", { type: "button", className: "tf-control-icon", onClick: onEdit, title: `Edit ${detailLabel.toLowerCase()} details`, "aria-label": `Edit ${detailLabel.toLowerCase()} details`, children: _jsx(Pencil, { size: 14, "aria-hidden": "true" }) })] })) })] }), archiveEntityConfirmationVisible && archiveDetailState.mode === 'ready' ? (_jsx(PlanningArchiveConfirmation, { entityType: detail.type, title: detail.item.title, activeTaskCount: detail.item.archiveActiveTaskCount, activeWorkstreamCount: detail.item.archiveActiveWorkstreamCount, busy: archiveEntityBusy, onCancel: () => setArchiveEntityConfirmationVisible(false), onConfirm: () => void handleConfirmArchiveEntity() })) : null, detail.type === 'workstream' ? renderRelationshipFeedback('workstream', detail.item.id) : null, inlineEditor ? (_jsx(PlanningDrawerEditor, { editor: inlineEditor.editor, draftTitle: inlineEditor.draftTitle, draftDescription: inlineEditor.draftDescription, draftOwner: inlineEditor.draftOwner, draftInitiativeId: inlineEditor.draftInitiativeId, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: inlineEditor.draftInitiativeSummary, onChangeDraftTitle: inlineEditor.onChangeDraftTitle, onChangeDraftDescription: inlineEditor.onChangeDraftDescription, onChangeDraftOwner: inlineEditor.onChangeDraftOwner, onChangeDraftInitiativeId: inlineEditor.onChangeDraftInitiativeId, onCancel: inlineEditor.onCancel, onSubmit: inlineEditor.onSubmit, isSubmitting: inlineEditor.isSubmitting, isSubmitDisabled: inlineEditor.isSubmitDisabled, inline: true, formId: inlineEditorFormId })) : (_jsxs(_Fragment, { children: [_jsxs("div", { className: panelStyles.detailTitleRow, children: [_jsx(PlanningVisualIdentity, { entityType: detail.type, entityId: detail.item.id, icon: detail.item.icon, color: detail.item.color, size: "detail", disabled: Boolean(detail.item.isArchived), onChange: onChangePlanningIdentity
|
|
776
776
|
? (patch) => onChangePlanningIdentity(detail.type, detail.item.id, patch)
|
|
777
777
|
: undefined }), _jsx("h3", { className: panelStyles.detailTitle, children: detail.item.title })] }), _jsx(PlanningEntityDescription, { description: detail.item.description, label: detailLabel, taskReferences: taskReferences, className: `${panelStyles.detailDescription} ${styles.appScrollbar} tf-scrollbar`.trim(), markdownClassName: panelStyles.detailDescriptionMarkdown })] })), _jsxs("div", { className: panelStyles.detailOwnerField, children: [_jsx("span", { className: panelStyles.detailOwnerLabel, children: "Owner" }), _jsx(PlanningOwnerDropdown, { value: ownerValue, disabled: !onChangeOwner || isSavingOwner, onChange: (nextOwnerId) => void handleOwnerChange(nextOwnerId), ariaLabel: `${detailLabel} owner`, options: assigneeOptions, className: `${planningDropdownStyles.control} ${panelStyles.ownerSelect}` })] }), detail.item.taskCount > 0 ? (_jsx(PlanningProgress, { value: detail.item.progressPercent, label: `${detail.item.progressPercent}% resolved`, statusCounts: detail.item.statusCounts })) : (_jsx(PlanningNoTasks, {})), _jsx(PlanningSummaryMeta, { item: detail.item, workstreamCount: detail.type === 'initiative'
|
|
778
778
|
? detail.item.workstreamCount ?? detail.item.workstreams.length
|
|
@@ -847,7 +847,7 @@ function PlanningDrawerDetailView({ detail, showArchivedChildren, inlineEditor,
|
|
|
847
847
|
}, title: "Archive task", "aria-label": `Archive task ${referenceLabel || task.title}`, children: _jsx(Archive, { size: 14, "aria-hidden": "true" }) })) : undefined }) }, task.id));
|
|
848
848
|
} })) : (_jsx("div", { className: panelStyles.passiveGuidance, children: "No tasks are linked to this workstream." }))), detachedRelationshipRecovery] }), _jsx("div", { className: panelStyles.listCard, children: _jsx(PlanningEntityContext, { entityType: detail.type, entityId: detail.item.id, referenceLabel: detailReferenceLabel, workspaceId: currentWorkspaceId, attachments: detail.item.attachments, readOnly: Boolean(detail.item.isArchived), onAddAttachment: (file) => onAddPlanningContextFile?.(detail.type, detail.item.id, file), onRemoveAttachment: (index) => onRemovePlanningContextFile?.(detail.type, detail.item.id, index), onUpdateAttachmentCaption: (index, caption) => (onUpdatePlanningContextCaption?.(detail.type, detail.item.id, index, caption)) }) }), _jsx("div", { className: panelStyles.listCard, children: _jsx(PlanningEntityActivity, { entityType: detail.type, item: detail.item, assigneeOptions: assigneeOptions.map(toPlanningAssigneeOption), currentActorId: currentActorId, taskReferences: taskReferences, parentInitiative: parentInitiative, listClassName: panelStyles.activityTimelineList, emptyClassName: panelStyles.activityTimelineEmpty, hydrationState: hydrationState, onRetryHydration: onRetryHydration }) }), _jsx(PlanningUnlinkConfirmation, { item: pendingUnlink, busy: isChangingRelationship, theme: theme, onCancel: () => setPendingUnlink(null), onConfirm: () => void handleConfirmUnlink() })] }));
|
|
849
849
|
}
|
|
850
|
-
function PlanningDrawerEditor({ editor, draftTitle, draftDescription, draftOwner, draftInitiativeId, draftIcon, draftColor, assigneeOptions, initiativeLinkOptions, draftInitiativeSummary, onChangeDraftTitle, onChangeDraftDescription, onChangeDraftOwner, onChangeDraftInitiativeId, onChangeDraftIcon, onChangeDraftColor, onCancel, onSubmit, isSubmitting, inline = false, formId, }) {
|
|
850
|
+
function PlanningDrawerEditor({ editor, draftTitle, draftDescription, draftOwner, draftInitiativeId, draftIcon, draftColor, assigneeOptions, initiativeLinkOptions, draftInitiativeSummary, onChangeDraftTitle, onChangeDraftDescription, onChangeDraftOwner, onChangeDraftInitiativeId, onChangeDraftIcon, onChangeDraftColor, onCancel, onSubmit, isSubmitting, isSubmitDisabled = false, inline = false, formId, }) {
|
|
851
851
|
const entityLabel = editor.entityType === 'initiative' ? 'Initiative' : 'Workstream';
|
|
852
852
|
const titleInputId = React.useId();
|
|
853
853
|
const titleErrorId = React.useId();
|
|
@@ -864,9 +864,11 @@ function PlanningDrawerEditor({ editor, draftTitle, draftDescription, draftOwner
|
|
|
864
864
|
titleInputRef.current?.focus();
|
|
865
865
|
return;
|
|
866
866
|
}
|
|
867
|
+
if (isSubmitDisabled)
|
|
868
|
+
return;
|
|
867
869
|
setTitleError(null);
|
|
868
870
|
onSubmit();
|
|
869
|
-
}, [draftTitle, entityLabel, onSubmit]);
|
|
871
|
+
}, [draftTitle, entityLabel, isSubmitDisabled, onSubmit]);
|
|
870
872
|
const handleKeyDown = React.useCallback((event) => {
|
|
871
873
|
if (!inline || event.key !== 'Escape' || isSubmitting)
|
|
872
874
|
return;
|
|
@@ -884,9 +886,9 @@ function PlanningDrawerEditor({ editor, draftTitle, draftDescription, draftOwner
|
|
|
884
886
|
onChangeDraftTitle(event.target.value);
|
|
885
887
|
}, placeholder: editor.entityType === 'initiative' ? 'Enter initiative title' : 'Enter workstream title' }), titleError ? _jsx("p", { id: titleErrorId, className: "tf-text-error", children: titleError }) : null] }), _jsxs("div", { className: panelStyles.editorField, children: [_jsxs("label", { className: panelStyles.editorLabel, htmlFor: descriptionInputId, children: [entityLabel, " description"] }), _jsx("textarea", { id: descriptionInputId, className: styles.textarea, disabled: isSubmitting, value: draftDescription, onChange: (event) => onChangeDraftDescription(event.target.value), placeholder: editor.entityType === 'initiative'
|
|
886
888
|
? 'Describe the broader outcome this initiative is meant to achieve.'
|
|
887
|
-
: 'Describe the lane of work this workstream will coordinate.', rows: 5 })] }), editor.entityType === 'workstream' && (_jsxs("div", { className: panelStyles.editorField, children: [_jsx("span", { className: panelStyles.editorLabel, children: "Initiative" }), _jsxs("div", { className: panelStyles.editorRelationshipRow, children: [_jsx(PlanningLinkPicker, { options: initiativeLinkOptions, selectedId: draftInitiativeSummary?.id || '', entityLabel: "initiative", disabled: isSubmitting, onSelect: (option) => onChangeDraftInitiativeId(option.referenceLabel) }), draftInitiativeId.trim() ? (_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", disabled: isSubmitting, onClick: () => onChangeDraftInitiativeId(''), children: "Remove" })) : null] })] })), !inline ? _jsxs("div", { className: panelStyles.editorField, children: [_jsxs("span", { className: panelStyles.editorLabel, children: [entityLabel, " owner"] }), _jsx(PlanningOwnerDropdown, { disabled: isSubmitting, value: draftOwner, onChange: onChangeDraftOwner, ariaLabel: `${entityLabel} owner`, options: assigneeOptions, className: panelStyles.editorSelect })] }) : null, !inline ? _jsxs("div", { className: panelStyles.editorActions, children: [_jsx("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: onCancel, disabled: isSubmitting, children: "Cancel" }), _jsx("button", { type: "submit", className: styles.primaryUpdateBtn, disabled: isSubmitting, children: submitLabel })] }) : null] }));
|
|
889
|
+
: 'Describe the lane of work this workstream will coordinate.', rows: 5 })] }), editor.entityType === 'workstream' && (_jsxs("div", { className: panelStyles.editorField, children: [_jsx("span", { className: panelStyles.editorLabel, children: "Initiative" }), _jsxs("div", { className: panelStyles.editorRelationshipRow, children: [_jsx(PlanningLinkPicker, { options: initiativeLinkOptions, selectedId: draftInitiativeSummary?.id || '', entityLabel: "initiative", disabled: isSubmitting, onSelect: (option) => onChangeDraftInitiativeId(option.referenceLabel) }), draftInitiativeId.trim() ? (_jsx("button", { type: "button", className: "tf-button-ghost tf-button-compact", disabled: isSubmitting, onClick: () => onChangeDraftInitiativeId(''), children: "Remove" })) : null] })] })), !inline ? _jsxs("div", { className: panelStyles.editorField, children: [_jsxs("span", { className: panelStyles.editorLabel, children: [entityLabel, " owner"] }), _jsx(PlanningOwnerDropdown, { disabled: isSubmitting, value: draftOwner, onChange: onChangeDraftOwner, ariaLabel: `${entityLabel} owner`, options: assigneeOptions, className: panelStyles.editorSelect })] }) : null, !inline ? _jsxs("div", { className: panelStyles.editorActions, children: [_jsx("button", { type: "button", className: styles.secondaryHeaderBtn, onClick: onCancel, disabled: isSubmitting, children: "Cancel" }), _jsx("button", { type: "submit", className: styles.primaryUpdateBtn, disabled: isSubmitting || isSubmitDisabled, children: submitLabel })] }) : null] }));
|
|
888
890
|
}
|
|
889
|
-
export function PlanningDrawer({ open, leftOffset = 0, returnFocusRef, onWidthChange, initiatives, standaloneWorkstreams, planningLoadState, onRetryPlanning, taskScope, activeInitiativeId, activeWorkstreamId, navigatorSort, navigatorSortReady, onNavigatorSortChange, taskArrangeMode = 'execution', onTaskArrangeModeChange = () => { }, onReorderWorkstreamTasks, planningFavorites, onTogglePlanningFavorite, favoritesSectionCollapsed, initiativesSectionCollapsed, workstreamsSectionCollapsed, onToggleFavoritesSection, onToggleInitiativesSection, onToggleWorkstreamsSection, expandedInitiativeIds, detail, editor, secondaryEditor = null, secondaryPane, primaryHydrationState, secondaryHydrationState, onRetryPrimaryHydration, onRetrySecondaryHydration, currentWorkspaceId, currentOwnerId, theme, assigneeOptions, linkOptions, showTaskCardStatusLabel = false, draftInitiativeSummary, draftTitle, draftDescription, draftOwner, draftInitiativeId, draftIcon = null, draftColor = null, onChangeDraftTitle, onChangeDraftDescription, onChangeDraftOwner, onChangeDraftInitiativeId, onChangeDraftIcon = () => { }, onChangeDraftColor = () => { }, onCollapseTreePane, onExpandTreePane, onCollapsePrimaryPane, onExpandPrimaryPane, onCollapseSecondaryPane, onExpandSecondaryPane, onBackFromSecondary, treeCollapsed, primaryCollapsed, secondaryCollapsed, onCancelEditor, onCancelSecondaryEditor, onSubmitEditor, isSubmittingEditor, onCreateInitiative, onCreateWorkstream, onToggleInitiative, onSelectInitiative, onSelectWorkstream, onOpenInitiativeDetails, onOpenWorkstreamDetails, onOpenNestedWorkstreamDetails, onEditInitiative, onEditWorkstream, onChangePlanningOwner, onChangePlanningIdentity = () => { }, onChangeTaskAssignee, onArchiveInitiative, onUnarchiveInitiative, onArchiveWorkstream, onUnarchiveWorkstream, onArchivePlanningTask, onArchiveResolvedWorkstream, onArchiveResolvedInitiative, onCreateTaskInWorkstream, onCreateWorkstreamInInitiative, onAssignInitiativeToWorkstream, onOpenTaskById, onSetTaskStatus, taskReferences, onAssignTaskToWorkstream, onAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile, onRemovePlanningContextFile, onUpdatePlanningContextCaption, }) {
|
|
891
|
+
export function PlanningDrawer({ open, leftOffset = 0, returnFocusRef, onWidthChange, initiatives, standaloneWorkstreams, planningLoadState, onRetryPlanning, taskScope, activeInitiativeId, activeWorkstreamId, navigatorSort, navigatorSortReady, onNavigatorSortChange, taskArrangeMode = 'execution', onTaskArrangeModeChange = () => { }, onReorderWorkstreamTasks, planningFavorites, onTogglePlanningFavorite, favoritesSectionCollapsed, initiativesSectionCollapsed, workstreamsSectionCollapsed, onToggleFavoritesSection, onToggleInitiativesSection, onToggleWorkstreamsSection, expandedInitiativeIds, detail, editor, secondaryEditor = null, secondaryPane, primaryHydrationState, secondaryHydrationState, onRetryPrimaryHydration, onRetrySecondaryHydration, currentWorkspaceId, currentOwnerId, theme, assigneeOptions, linkOptions, showTaskCardStatusLabel = false, draftInitiativeSummary, draftTitle, draftDescription, draftOwner, draftInitiativeId, draftIcon = null, draftColor = null, onChangeDraftTitle, onChangeDraftDescription, onChangeDraftOwner, onChangeDraftInitiativeId, onChangeDraftIcon = () => { }, onChangeDraftColor = () => { }, onCollapseTreePane, onExpandTreePane, onCollapsePrimaryPane, onExpandPrimaryPane, onCollapseSecondaryPane, onExpandSecondaryPane, onBackFromSecondary, treeCollapsed, primaryCollapsed, secondaryCollapsed, onCancelEditor, onCancelSecondaryEditor, onSubmitEditor, isSubmittingEditor, isEditorSubmitDisabled = false, onCreateInitiative, onCreateWorkstream, onToggleInitiative, onSelectInitiative, onSelectWorkstream, onOpenInitiativeDetails, onOpenWorkstreamDetails, onOpenNestedWorkstreamDetails, onEditInitiative, onEditWorkstream, onChangePlanningOwner, onChangePlanningIdentity = () => { }, onChangeTaskAssignee, onArchiveInitiative, onUnarchiveInitiative, onArchiveWorkstream, onUnarchiveWorkstream, onArchivePlanningTask, onArchiveResolvedWorkstream, onArchiveResolvedInitiative, onCreateTaskInWorkstream, onCreateWorkstreamInInitiative, onAssignInitiativeToWorkstream, onOpenTaskById, onSetTaskStatus, taskReferences, onAssignTaskToWorkstream, onAttachTaskToWorkstreamByReference, onAttachWorkstreamToInitiativeByReference, onAddPlanningContextFile, onRemovePlanningContextFile, onUpdatePlanningContextCaption, }) {
|
|
890
892
|
const primaryInlineEditor = editor?.mode === 'edit'
|
|
891
893
|
&& detail
|
|
892
894
|
&& editor.entityType === detail.type
|
|
@@ -1085,7 +1087,7 @@ export function PlanningDrawer({ open, leftOffset = 0, returnFocusRef, onWidthCh
|
|
|
1085
1087
|
pointerEvents: open ? 'auto' : 'none',
|
|
1086
1088
|
transition: 'left 220ms cubic-bezier(0.4, 0, 0.2, 1)',
|
|
1087
1089
|
willChange: 'left',
|
|
1088
|
-
}, children: _jsxs("div", { className: panelStyles.drawerBody, children: [showTreePane && (effectiveTreeCollapsed ? (_jsx(CollapsedPane, { label: "Planning", onExpand: onExpandTreePane })) : (_jsxs("div", { ref: treePaneRef, "data-planning-pane": "tree", className: `${panelStyles.paneShell} ${styles.appScrollbar} tf-scrollbar ${panelStyles.treePane} ${hasPrimaryPane && !primaryCollapsed ? panelStyles.treePaneSplit : ''}`.trim(), style: paneWidthStyle(layoutMode === 'narrow' ? drawerWidth : PLANNING_DRAWER_TREE_WIDTH_PX), children: [_jsxs("div", { className: panelStyles.paneHeader, children: [_jsx("h2", { className: panelStyles.paneHeaderLabel, tabIndex: -1, "data-planning-pane-heading": true, children: "Planning" }), _jsxs("div", { className: panelStyles.headerActionGroup, children: [_jsx(ArchiveVisibilityToggle, { visible: showArchivedPlanning, disabled: taskScope === 'deleted' || archivedPlanningItemCount === 0, disabledLabel: taskScope === 'deleted' ? 'Archive visibility unavailable in Trash' : undefined, itemLabel: "planning items", onToggle: () => setShowArchivedPlanning((current) => !current) }), wideLayout ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: onCollapseTreePane, "aria-label": "Collapse Planning pane", title: "Collapse Planning pane", children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null] })] }), _jsx("div", { className: panelStyles.paneContent, "aria-busy": planningLoadState.status === 'loading' || planningLoadState.isRefreshing, children: _jsx(PlanningDrawerList, { initiatives: initiatives, standaloneWorkstreams: standaloneWorkstreams, planningLoadState: planningLoadState, taskScope: taskScope, showArchivedPlanning: showArchivedPlanning, currentOwnerId: currentOwnerId, assigneeOptions: assigneeOptions, showTaskCardStatusLabel: showTaskCardStatusLabel, taskArrangeMode: taskArrangeMode, activeInitiativeId: activeInitiativeId, activeWorkstreamId: activeWorkstreamId, navigatorSort: navigatorSort, navigatorSortReady: navigatorSortReady, onNavigatorSortChange: onNavigatorSortChange, planningFavorites: planningFavorites, onTogglePlanningFavorite: onTogglePlanningFavorite, favoritesSectionCollapsed: favoritesSectionCollapsed, initiativesSectionCollapsed: initiativesSectionCollapsed, workstreamsSectionCollapsed: workstreamsSectionCollapsed, onToggleFavoritesSection: onToggleFavoritesSection, onToggleInitiativesSection: onToggleInitiativesSection, onToggleWorkstreamsSection: onToggleWorkstreamsSection, expandedInitiativeIds: expandedInitiativeIds, detail: detail, secondaryPane: secondaryPane, onCreateInitiative: onCreateInitiative, onCreateWorkstream: onCreateWorkstream, onToggleInitiative: onToggleInitiative, onSelectInitiative: onSelectInitiative, onSelectWorkstream: onSelectWorkstream, onOpenInitiativeDetails: onOpenInitiativeDetails, onOpenWorkstreamDetails: onOpenWorkstreamDetails, onOpenNestedWorkstreamDetails: onOpenNestedWorkstreamDetails, onOpenTaskById: onOpenTaskById, onSetTaskStatus: onSetTaskStatus, onChangePlanningOwner: onChangePlanningOwner, onChangePlanningIdentity: onChangePlanningIdentity, onChangeTaskAssignee: onChangeTaskAssignee, onArchivePlanningTask: onArchivePlanningTask, onArchiveResolvedWorkstream: onArchiveResolvedWorkstream, onArchiveResolvedInitiative: onArchiveResolvedInitiative, linkOptions: linkOptions, onAssignInitiativeToWorkstream: onAssignInitiativeToWorkstream, onRetryPlanning: onRetryPlanning }) })] }))), wideLayout && !primaryIsInitiative && (initiativeRelationshipExpanded ? (_jsx(RelationshipPane, { label: "Initiative", title: initiativeRelationship.title, description: initiativeRelationship.description, action: initiativeRelationship.action, onCollapse: () => setInitiativeRelationshipExpanded(false), paneRef: initiativeRelationshipPaneRef })) : (_jsx(CollapsedPane, { label: "Initiative", muted: !hasInitiativeRelationship, onExpand: () => setInitiativeRelationshipExpanded(true) }))), showPrimaryPane && (effectivePrimaryCollapsed ? (_jsx(CollapsedPane, { label: primaryEntityType === 'initiative' ? 'Initiative' : 'Workstream', onExpand: onExpandPrimaryPane })) : (_jsxs("div", { ref: primaryPaneRef, "data-planning-pane": "primary", className: `${panelStyles.paneShell} ${styles.appScrollbar} tf-scrollbar ${panelStyles.detailPane}`.trim(), style: paneWidthStyle(wideLayout ? PLANNING_DRAWER_DETAIL_WIDTH_PX : adaptiveDetailWidth), children: [_jsxs("div", { className: panelStyles.paneHeader, children: [layoutMode === 'narrow' ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: handleBackFromPrimary, "aria-label": "Back to Planning", title: "Back to Planning", children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null, _jsx("h2", { className: panelStyles.paneHeaderLabel, tabIndex: -1, "data-planning-pane-heading": true, children: primaryPaneLabel }), _jsxs("div", { className: panelStyles.headerActionGroup, children: [primaryArchiveDetail ? (_jsx(ArchiveVisibilityToggle, { visible: archivedDetailVisibility[primaryArchiveDetail.type], disabled: countArchivedDetailChildren(primaryArchiveDetail) === 0, itemLabel: primaryArchiveDetail.type === 'initiative' ? 'workstreams' : 'tasks', onToggle: () => toggleArchivedDetailVisibility(primaryArchiveDetail.type) })) : null, wideLayout ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: onCollapsePrimaryPane, "aria-label": `Collapse ${primaryPaneLabel} pane`, title: `Collapse ${primaryPaneLabel} pane`, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null] })] }), _jsx("div", { className: panelStyles.paneContent, "aria-busy": primaryHydrationState?.status === 'loading', children: dedicatedPrimaryEditor ? (_jsx(PlanningDrawerEditor, { editor: dedicatedPrimaryEditor, draftTitle: draftTitle, draftDescription: draftDescription, draftOwner: draftOwner, draftInitiativeId: draftInitiativeId, draftIcon: draftIcon, draftColor: draftColor, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: draftInitiativeSummary, onChangeDraftTitle: onChangeDraftTitle, onChangeDraftDescription: onChangeDraftDescription, onChangeDraftOwner: onChangeDraftOwner, onChangeDraftInitiativeId: onChangeDraftInitiativeId, onChangeDraftIcon: onChangeDraftIcon, onChangeDraftColor: onChangeDraftColor, onCancel: onCancelEditor, onSubmit: onSubmitEditor, isSubmitting: isSubmittingEditor })) : detail ? (_jsx(PlanningDrawerDetailView, { detail: detail, showArchivedChildren: archivedDetailVisibility[detail.type], inlineEditor: primaryInlineEditor ? {
|
|
1090
|
+
}, children: _jsxs("div", { className: panelStyles.drawerBody, children: [showTreePane && (effectiveTreeCollapsed ? (_jsx(CollapsedPane, { label: "Planning", onExpand: onExpandTreePane })) : (_jsxs("div", { ref: treePaneRef, "data-planning-pane": "tree", className: `${panelStyles.paneShell} ${styles.appScrollbar} tf-scrollbar ${panelStyles.treePane} ${hasPrimaryPane && !primaryCollapsed ? panelStyles.treePaneSplit : ''}`.trim(), style: paneWidthStyle(layoutMode === 'narrow' ? drawerWidth : PLANNING_DRAWER_TREE_WIDTH_PX), children: [_jsxs("div", { className: panelStyles.paneHeader, children: [_jsx("h2", { className: panelStyles.paneHeaderLabel, tabIndex: -1, "data-planning-pane-heading": true, children: "Planning" }), _jsxs("div", { className: panelStyles.headerActionGroup, children: [_jsx(ArchiveVisibilityToggle, { visible: showArchivedPlanning, disabled: taskScope === 'deleted' || archivedPlanningItemCount === 0, disabledLabel: taskScope === 'deleted' ? 'Archive visibility unavailable in Trash' : undefined, itemLabel: "planning items", onToggle: () => setShowArchivedPlanning((current) => !current) }), wideLayout ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: onCollapseTreePane, "aria-label": "Collapse Planning pane", title: "Collapse Planning pane", children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null] })] }), _jsx("div", { className: panelStyles.paneContent, "aria-busy": planningLoadState.status === 'loading' || planningLoadState.isRefreshing, children: _jsx(PlanningDrawerList, { initiatives: initiatives, standaloneWorkstreams: standaloneWorkstreams, planningLoadState: planningLoadState, taskScope: taskScope, showArchivedPlanning: showArchivedPlanning, currentOwnerId: currentOwnerId, assigneeOptions: assigneeOptions, showTaskCardStatusLabel: showTaskCardStatusLabel, taskArrangeMode: taskArrangeMode, activeInitiativeId: activeInitiativeId, activeWorkstreamId: activeWorkstreamId, navigatorSort: navigatorSort, navigatorSortReady: navigatorSortReady, onNavigatorSortChange: onNavigatorSortChange, planningFavorites: planningFavorites, onTogglePlanningFavorite: onTogglePlanningFavorite, favoritesSectionCollapsed: favoritesSectionCollapsed, initiativesSectionCollapsed: initiativesSectionCollapsed, workstreamsSectionCollapsed: workstreamsSectionCollapsed, onToggleFavoritesSection: onToggleFavoritesSection, onToggleInitiativesSection: onToggleInitiativesSection, onToggleWorkstreamsSection: onToggleWorkstreamsSection, expandedInitiativeIds: expandedInitiativeIds, detail: detail, secondaryPane: secondaryPane, onCreateInitiative: onCreateInitiative, onCreateWorkstream: onCreateWorkstream, onToggleInitiative: onToggleInitiative, onSelectInitiative: onSelectInitiative, onSelectWorkstream: onSelectWorkstream, onOpenInitiativeDetails: onOpenInitiativeDetails, onOpenWorkstreamDetails: onOpenWorkstreamDetails, onOpenNestedWorkstreamDetails: onOpenNestedWorkstreamDetails, onOpenTaskById: onOpenTaskById, onSetTaskStatus: onSetTaskStatus, onChangePlanningOwner: onChangePlanningOwner, onChangePlanningIdentity: onChangePlanningIdentity, onChangeTaskAssignee: onChangeTaskAssignee, onArchivePlanningTask: onArchivePlanningTask, onArchiveResolvedWorkstream: onArchiveResolvedWorkstream, onArchiveResolvedInitiative: onArchiveResolvedInitiative, linkOptions: linkOptions, onAssignInitiativeToWorkstream: onAssignInitiativeToWorkstream, onRetryPlanning: onRetryPlanning }) })] }))), wideLayout && !primaryIsInitiative && (initiativeRelationshipExpanded ? (_jsx(RelationshipPane, { label: "Initiative", title: initiativeRelationship.title, description: initiativeRelationship.description, action: initiativeRelationship.action, onCollapse: () => setInitiativeRelationshipExpanded(false), paneRef: initiativeRelationshipPaneRef })) : (_jsx(CollapsedPane, { label: "Initiative", muted: !hasInitiativeRelationship, onExpand: () => setInitiativeRelationshipExpanded(true) }))), showPrimaryPane && (effectivePrimaryCollapsed ? (_jsx(CollapsedPane, { label: primaryEntityType === 'initiative' ? 'Initiative' : 'Workstream', onExpand: onExpandPrimaryPane })) : (_jsxs("div", { ref: primaryPaneRef, "data-planning-pane": "primary", className: `${panelStyles.paneShell} ${styles.appScrollbar} tf-scrollbar ${panelStyles.detailPane}`.trim(), style: paneWidthStyle(wideLayout ? PLANNING_DRAWER_DETAIL_WIDTH_PX : adaptiveDetailWidth), children: [_jsxs("div", { className: panelStyles.paneHeader, children: [layoutMode === 'narrow' ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: handleBackFromPrimary, "aria-label": "Back to Planning", title: "Back to Planning", children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null, _jsx("h2", { className: panelStyles.paneHeaderLabel, tabIndex: -1, "data-planning-pane-heading": true, children: primaryPaneLabel }), _jsxs("div", { className: panelStyles.headerActionGroup, children: [primaryArchiveDetail ? (_jsx(ArchiveVisibilityToggle, { visible: archivedDetailVisibility[primaryArchiveDetail.type], disabled: countArchivedDetailChildren(primaryArchiveDetail) === 0, itemLabel: primaryArchiveDetail.type === 'initiative' ? 'workstreams' : 'tasks', onToggle: () => toggleArchivedDetailVisibility(primaryArchiveDetail.type) })) : null, wideLayout ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: onCollapsePrimaryPane, "aria-label": `Collapse ${primaryPaneLabel} pane`, title: `Collapse ${primaryPaneLabel} pane`, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null] })] }), _jsx("div", { className: panelStyles.paneContent, "aria-busy": primaryHydrationState?.status === 'loading', children: dedicatedPrimaryEditor ? (_jsx(PlanningDrawerEditor, { editor: dedicatedPrimaryEditor, draftTitle: draftTitle, draftDescription: draftDescription, draftOwner: draftOwner, draftInitiativeId: draftInitiativeId, draftIcon: draftIcon, draftColor: draftColor, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: draftInitiativeSummary, onChangeDraftTitle: onChangeDraftTitle, onChangeDraftDescription: onChangeDraftDescription, onChangeDraftOwner: onChangeDraftOwner, onChangeDraftInitiativeId: onChangeDraftInitiativeId, onChangeDraftIcon: onChangeDraftIcon, onChangeDraftColor: onChangeDraftColor, onCancel: onCancelEditor, onSubmit: onSubmitEditor, isSubmitting: isSubmittingEditor, isSubmitDisabled: isEditorSubmitDisabled })) : detail ? (_jsx(PlanningDrawerDetailView, { detail: detail, showArchivedChildren: archivedDetailVisibility[detail.type], inlineEditor: primaryInlineEditor ? {
|
|
1089
1091
|
editor: primaryInlineEditor,
|
|
1090
1092
|
draftTitle,
|
|
1091
1093
|
draftDescription,
|
|
@@ -1099,6 +1101,7 @@ export function PlanningDrawer({ open, leftOffset = 0, returnFocusRef, onWidthCh
|
|
|
1099
1101
|
onCancel: onCancelEditor,
|
|
1100
1102
|
onSubmit: onSubmitEditor,
|
|
1101
1103
|
isSubmitting: isSubmittingEditor,
|
|
1104
|
+
isSubmitDisabled: isEditorSubmitDisabled,
|
|
1102
1105
|
} : null, selectedChildWorkstreamId: secondaryPane?.kind === 'detail'
|
|
1103
1106
|
? secondaryPane.detail.item.id
|
|
1104
1107
|
: null, currentWorkspaceId: currentWorkspaceId, currentActorId: currentOwnerId, theme: theme, assigneeOptions: assigneeOptions, linkOptions: linkOptions, showTaskCardStatusLabel: showTaskCardStatusLabel, taskArrangeMode: taskArrangeMode, onTaskArrangeModeChange: onTaskArrangeModeChange, onReorderWorkstreamTasks: onReorderWorkstreamTasks, hydrationState: primaryHydrationState, onRetryHydration: onRetryPrimaryHydration, onSetTaskStatus: detail.type === 'workstream' ? onSetTaskStatus : undefined, onArchivePlanningTask: detail.type === 'workstream' ? onArchivePlanningTask : undefined, onArchiveResolvedWorkstream: detail.type === 'initiative' ? onArchiveResolvedWorkstream : undefined, onArchiveResolvedEntity: detail.type === 'initiative'
|
|
@@ -1111,7 +1114,7 @@ export function PlanningDrawer({ open, leftOffset = 0, returnFocusRef, onWidthCh
|
|
|
1111
1114
|
? onUnarchiveInitiative(detail.item.id)
|
|
1112
1115
|
: onUnarchiveWorkstream(detail.item.id), onAddPlanningContextFile: onAddPlanningContextFile, onRemovePlanningContextFile: onRemovePlanningContextFile, onUpdatePlanningContextCaption: onUpdatePlanningContextCaption })) : null })] }))), wideLayout && !primaryIsWorkstream && !hasSecondaryPane && (workstreamRelationshipExpanded ? (_jsx(RelationshipPane, { label: "Workstream", title: workstreamRelationship.title, description: workstreamRelationship.description, action: workstreamRelationship.action, onCollapse: () => setWorkstreamRelationshipExpanded(false), paneRef: workstreamRelationshipPaneRef })) : (_jsx(CollapsedPane, { label: "Workstream", muted: true, onExpand: () => setWorkstreamRelationshipExpanded(true) }))), showSecondaryPane && (effectiveSecondaryCollapsed ? (_jsx(CollapsedPane, { label: "Workstream", onExpand: onExpandSecondaryPane })) : (_jsxs("div", { ref: secondaryPaneRef, "data-planning-pane": "secondary", className: `${panelStyles.paneShell} ${styles.appScrollbar} tf-scrollbar ${panelStyles.detailPane}`.trim(), style: paneWidthStyle(wideLayout ? PLANNING_DRAWER_DETAIL_WIDTH_PX : adaptiveDetailWidth), children: [_jsxs("div", { className: panelStyles.paneHeader, children: [!wideLayout ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: secondaryInlineEditor
|
|
1113
1116
|
? (onCancelSecondaryEditor || onBackFromSecondary)
|
|
1114
|
-
: onBackFromSecondary, "aria-label": secondaryInlineEditor ? 'Cancel workstream editing' : `Back to ${primaryPaneLabel}`, title: secondaryInlineEditor ? 'Cancel workstream editing' : `Back to ${primaryPaneLabel}`, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null, _jsx("h2", { className: panelStyles.paneHeaderLabel, tabIndex: -1, "data-planning-pane-heading": true, children: secondaryPaneLabel }), _jsxs("div", { className: panelStyles.headerActionGroup, children: [secondaryArchiveDetail ? (_jsx(ArchiveVisibilityToggle, { visible: archivedDetailVisibility.workstream, disabled: countArchivedDetailChildren(secondaryArchiveDetail) === 0, itemLabel: "tasks", onToggle: () => toggleArchivedDetailVisibility('workstream') })) : null, wideLayout ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: onCollapseSecondaryPane, "aria-label": `Collapse ${secondaryPaneLabel} pane`, title: `Collapse ${secondaryPaneLabel} pane`, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null] })] }), _jsx("div", { className: panelStyles.paneContent, "aria-busy": secondaryHydrationState?.status === 'loading', children: secondaryPane?.kind === 'editor' ? (_jsx(PlanningDrawerEditor, { editor: secondaryPane.editor, draftTitle: draftTitle, draftDescription: draftDescription, draftOwner: draftOwner, draftInitiativeId: draftInitiativeId, draftIcon: draftIcon, draftColor: draftColor, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: draftInitiativeSummary, onChangeDraftTitle: onChangeDraftTitle, onChangeDraftDescription: onChangeDraftDescription, onChangeDraftOwner: onChangeDraftOwner, onChangeDraftInitiativeId: onChangeDraftInitiativeId, onChangeDraftIcon: onChangeDraftIcon, onChangeDraftColor: onChangeDraftColor, onCancel: onBackFromSecondary, onSubmit: onSubmitEditor, isSubmitting: isSubmittingEditor })) : secondaryPane ? (_jsx(PlanningDrawerDetailView, { detail: secondaryPane.detail, showArchivedChildren: archivedDetailVisibility.workstream, inlineEditor: secondaryInlineEditor ? {
|
|
1117
|
+
: onBackFromSecondary, "aria-label": secondaryInlineEditor ? 'Cancel workstream editing' : `Back to ${primaryPaneLabel}`, title: secondaryInlineEditor ? 'Cancel workstream editing' : `Back to ${primaryPaneLabel}`, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null, _jsx("h2", { className: panelStyles.paneHeaderLabel, tabIndex: -1, "data-planning-pane-heading": true, children: secondaryPaneLabel }), _jsxs("div", { className: panelStyles.headerActionGroup, children: [secondaryArchiveDetail ? (_jsx(ArchiveVisibilityToggle, { visible: archivedDetailVisibility.workstream, disabled: countArchivedDetailChildren(secondaryArchiveDetail) === 0, itemLabel: "tasks", onToggle: () => toggleArchivedDetailVisibility('workstream') })) : null, wideLayout ? (_jsx("button", { type: "button", className: "tf-control-icon", onClick: onCollapseSecondaryPane, "aria-label": `Collapse ${secondaryPaneLabel} pane`, title: `Collapse ${secondaryPaneLabel} pane`, children: _jsx(ChevronLeft, { size: 16, "aria-hidden": "true" }) })) : null] })] }), _jsx("div", { className: panelStyles.paneContent, "aria-busy": secondaryHydrationState?.status === 'loading', children: secondaryPane?.kind === 'editor' ? (_jsx(PlanningDrawerEditor, { editor: secondaryPane.editor, draftTitle: draftTitle, draftDescription: draftDescription, draftOwner: draftOwner, draftInitiativeId: draftInitiativeId, draftIcon: draftIcon, draftColor: draftColor, assigneeOptions: assigneeOptions, initiativeLinkOptions: linkOptions.initiatives, draftInitiativeSummary: draftInitiativeSummary, onChangeDraftTitle: onChangeDraftTitle, onChangeDraftDescription: onChangeDraftDescription, onChangeDraftOwner: onChangeDraftOwner, onChangeDraftInitiativeId: onChangeDraftInitiativeId, onChangeDraftIcon: onChangeDraftIcon, onChangeDraftColor: onChangeDraftColor, onCancel: onBackFromSecondary, onSubmit: onSubmitEditor, isSubmitting: isSubmittingEditor, isSubmitDisabled: isEditorSubmitDisabled })) : secondaryPane ? (_jsx(PlanningDrawerDetailView, { detail: secondaryPane.detail, showArchivedChildren: archivedDetailVisibility.workstream, inlineEditor: secondaryInlineEditor ? {
|
|
1115
1118
|
editor: secondaryInlineEditor,
|
|
1116
1119
|
draftTitle,
|
|
1117
1120
|
draftDescription,
|
|
@@ -1125,5 +1128,6 @@ export function PlanningDrawer({ open, leftOffset = 0, returnFocusRef, onWidthCh
|
|
|
1125
1128
|
onCancel: onCancelSecondaryEditor || onBackFromSecondary,
|
|
1126
1129
|
onSubmit: onSubmitEditor,
|
|
1127
1130
|
isSubmitting: isSubmittingEditor,
|
|
1131
|
+
isSubmitDisabled: isEditorSubmitDisabled,
|
|
1128
1132
|
} : null, currentWorkspaceId: currentWorkspaceId, currentActorId: currentOwnerId, theme: theme, assigneeOptions: assigneeOptions, linkOptions: linkOptions, showTaskCardStatusLabel: showTaskCardStatusLabel, taskArrangeMode: taskArrangeMode, onTaskArrangeModeChange: onTaskArrangeModeChange, onReorderWorkstreamTasks: onReorderWorkstreamTasks, hydrationState: secondaryHydrationState, onRetryHydration: onRetrySecondaryHydration, onSetTaskStatus: onSetTaskStatus, onArchivePlanningTask: onArchivePlanningTask, onArchiveResolvedEntity: onArchiveResolvedWorkstream, onCreateTaskInWorkstream: onCreateTaskInWorkstream, onOpenTaskById: onOpenTaskById, onChangeTaskAssignee: onChangeTaskAssignee, taskReferences: taskReferences, onAssignTaskToWorkstream: onAssignTaskToWorkstream, onAssignInitiativeToWorkstream: onAssignInitiativeToWorkstream, onAttachTaskToWorkstreamByReference: onAttachTaskToWorkstreamByReference, parentInitiative: resolveParentInitiative(secondaryPane.detail.item), onEdit: () => onEditWorkstream(secondaryPane.detail.item.id), onChangeOwner: (ownerId) => onChangePlanningOwner('workstream', secondaryPane.detail.item.id, ownerId), onChangePlanningOwner: onChangePlanningOwner, onChangePlanningIdentity: onChangePlanningIdentity, onArchive: () => onArchiveWorkstream(secondaryPane.detail.item.id), onUnarchive: () => onUnarchiveWorkstream(secondaryPane.detail.item.id), onAddPlanningContextFile: onAddPlanningContextFile, onRemovePlanningContextFile: onRemovePlanningContextFile, onUpdatePlanningContextCaption: onUpdatePlanningContextCaption })) : null })] })))] }) }));
|
|
1129
1133
|
}
|
package/dist/config/envSchema.js
CHANGED
|
@@ -172,6 +172,12 @@ const SPECS = [
|
|
|
172
172
|
{ key: 'TASKFORCE_ADMIN_AUDIT_SYNC_RETENTION_DAYS', description: 'Retention period for non-routine sync audit failures and exceptional events.', category: 'Security', defaultValue: '30', targets: ['server', 'all'] },
|
|
173
173
|
{ key: 'TASKFORCE_ADMIN_AUDIT_RETENTION_DAYS', description: 'Default retention period for administrative and security audit events.', category: 'Security', defaultValue: '365', targets: ['server', 'all'] },
|
|
174
174
|
{ key: 'TASKFORCE_SYNC_KEY', description: 'Optional sync encryption key.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
175
|
+
{ key: 'TASKFORCE_GITHUB_APP_ID', description: 'GitHub App identifier for the Taskforce GitHub integration.', category: 'Security', targets: ['server', 'all'] },
|
|
176
|
+
{ key: 'TASKFORCE_GITHUB_APP_PRIVATE_KEY_BASE64', description: 'Base64-encoded private key for the Taskforce GitHub App.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
177
|
+
{ key: 'TASKFORCE_GITHUB_APP_SLUG', description: 'Public slug for the Taskforce GitHub App.', category: 'Security', targets: ['server', 'all'] },
|
|
178
|
+
{ key: 'TASKFORCE_GITHUB_APP_CLIENT_ID', description: 'OAuth client identifier for the Taskforce GitHub App.', category: 'Security', targets: ['server', 'all'] },
|
|
179
|
+
{ key: 'TASKFORCE_GITHUB_APP_CLIENT_SECRET', description: 'OAuth client secret for the Taskforce GitHub App.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
180
|
+
{ key: 'TASKFORCE_GITHUB_APP_WEBHOOK_SECRET', description: 'Webhook signature secret for the Taskforce GitHub App.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
175
181
|
{ key: 'TASKFORCE_GITHUB_REVIEW_APP_ID', description: 'GitHub App identifier used for private-repository review evidence.', category: 'Security', targets: ['server', 'all'] },
|
|
176
182
|
{ key: 'TASKFORCE_GITHUB_REVIEW_APP_PRIVATE_KEY_BASE64', description: 'Base64-encoded GitHub App private key used only to mint short-lived installation tokens.', category: 'Security', secret: true, targets: ['server', 'all'] },
|
|
177
183
|
{ key: 'TASKFORCE_PHASE_A_APPROVAL_ISSUER_ENABLED', description: 'Enable performance-cloud Phase A approval receipt issuance.', category: 'Security', defaultValue: 'false', targets: ['server', 'all'] },
|
|
@@ -206,6 +212,7 @@ const SPECS = [
|
|
|
206
212
|
{ key: 'SYNC_SOAK_DURATION_MIN', description: 'Soak duration in minutes.', category: 'Soak', defaultValue: '60', targets: ['soak', 'all'] },
|
|
207
213
|
{ key: 'SYNC_SOAK_SEED', description: 'Deterministic soak seed.', category: 'Soak', example: '4242', targets: ['soak', 'all'] },
|
|
208
214
|
{ key: 'SYNC_SOAK_TASK_ID_NAMESPACE', description: 'Optional per-run namespace for deterministic generated soak task IDs.', category: 'Soak', targets: ['soak', 'all'] },
|
|
215
|
+
{ key: 'SYNC_SOAK_RUNTIME_ROOT', description: 'Root directory for isolated local soak runtimes and cleanup verification.', category: 'Soak', targets: ['soak', 'all'] },
|
|
209
216
|
{ key: 'SYNC_SOAK_ARTIFACT_DIR', description: 'Soak artifacts output directory.', category: 'Soak', defaultValue: '.taskforce/soak-artifacts', targets: ['soak', 'all'] },
|
|
210
217
|
{ key: 'SYNC_SOAK_ENABLE_SYNC_DEBUG', description: 'Enable sync debug diagnostics during soak.', category: 'Soak', defaultValue: 'false', targets: ['soak', 'all'] },
|
|
211
218
|
{ key: 'SYNC_SOAK_STRESS_ACTIONS', description: 'Stress action count.', category: 'Soak', defaultValue: '150', targets: ['soak', 'all'] },
|
|
@@ -24,6 +24,7 @@ export declare class McpTokenService {
|
|
|
24
24
|
private createMcpAccessTokenPrefix;
|
|
25
25
|
private parseMcpAccessToken;
|
|
26
26
|
private normalizeMcpOAuthProvider;
|
|
27
|
+
private normalizeMcpOAuthApplicationType;
|
|
27
28
|
private defaultMcpOAuthClientName;
|
|
28
29
|
private createMcpOAuthCodePrefix;
|
|
29
30
|
private createMcpOAuthAccessTokenPrefix;
|
|
@@ -96,6 +97,7 @@ export declare class McpTokenService {
|
|
|
96
97
|
provider: McpOAuthProvider | string;
|
|
97
98
|
clientName: string;
|
|
98
99
|
redirectUris: unknown;
|
|
100
|
+
applicationType?: unknown;
|
|
99
101
|
productClientId?: string | null;
|
|
100
102
|
}): McpOAuthClientRecord;
|
|
101
103
|
getMcpOAuthClient(clientId: string, provider?: McpOAuthProvider | string | null): McpOAuthClientRecord | null;
|
|
@@ -98,6 +98,12 @@ export class McpTokenService {
|
|
|
98
98
|
return 'perplexity';
|
|
99
99
|
throw new TaskforceRuleError('Unsupported MCP OAuth provider', 400, 'MCP_OAUTH_PROVIDER_INVALID');
|
|
100
100
|
}
|
|
101
|
+
normalizeMcpOAuthApplicationType(raw) {
|
|
102
|
+
const normalized = String(raw || 'web').trim().toLowerCase();
|
|
103
|
+
if (normalized === 'native' || normalized === 'web')
|
|
104
|
+
return normalized;
|
|
105
|
+
throw new TaskforceRuleError('application_type must be native or web', 400, 'MCP_OAUTH_APPLICATION_TYPE_INVALID');
|
|
106
|
+
}
|
|
101
107
|
defaultMcpOAuthClientName(provider) {
|
|
102
108
|
if (provider === 'claude')
|
|
103
109
|
return 'Claude Chat Connector';
|
|
@@ -169,14 +175,14 @@ export class McpTokenService {
|
|
|
169
175
|
return null;
|
|
170
176
|
if (provider) {
|
|
171
177
|
return this.db.prepare(`
|
|
172
|
-
SELECT id, provider, client_name, redirect_uris_json, product_client_id, status, created_at, updated_at
|
|
178
|
+
SELECT id, provider, client_name, redirect_uris_json, application_type, product_client_id, status, created_at, updated_at
|
|
173
179
|
FROM mcp_oauth_clients
|
|
174
180
|
WHERE tenant_id = ? AND id = ? AND provider = ?
|
|
175
181
|
LIMIT 1
|
|
176
182
|
`).get(this.tenantId, normalizedClientId, provider) || null;
|
|
177
183
|
}
|
|
178
184
|
return this.db.prepare(`
|
|
179
|
-
SELECT id, provider, client_name, redirect_uris_json, product_client_id, status, created_at, updated_at
|
|
185
|
+
SELECT id, provider, client_name, redirect_uris_json, application_type, product_client_id, status, created_at, updated_at
|
|
180
186
|
FROM mcp_oauth_clients
|
|
181
187
|
WHERE tenant_id = ? AND id = ?
|
|
182
188
|
LIMIT 1
|
|
@@ -194,6 +200,7 @@ export class McpTokenService {
|
|
|
194
200
|
id: String(row?.id || ''),
|
|
195
201
|
provider: this.normalizeMcpOAuthProvider(row?.provider),
|
|
196
202
|
clientName: String(row?.client_name || ''),
|
|
203
|
+
applicationType: this.normalizeMcpOAuthApplicationType(row?.application_type),
|
|
197
204
|
productClientId: row?.product_client_id ? String(row.product_client_id) : null,
|
|
198
205
|
redirectUris,
|
|
199
206
|
status: String(row?.status || '').trim().toLowerCase() === 'revoked' ? 'revoked' : 'active',
|
|
@@ -716,15 +723,16 @@ export class McpTokenService {
|
|
|
716
723
|
const provider = this.normalizeMcpOAuthProvider(input.provider);
|
|
717
724
|
const clientName = String(input.clientName || '').trim() || this.defaultMcpOAuthClientName(provider);
|
|
718
725
|
const redirectUris = this.normalizeRedirectUris(input.redirectUris);
|
|
726
|
+
const applicationType = this.normalizeMcpOAuthApplicationType(input.applicationType);
|
|
719
727
|
const productClientId = String(input.productClientId || '').trim() || null;
|
|
720
728
|
const now = new Date().toISOString();
|
|
721
729
|
const clientId = this.generateEntityId(`mcp-oauth-client-${provider}`);
|
|
722
730
|
this.db.prepare(`
|
|
723
731
|
INSERT INTO mcp_oauth_clients (
|
|
724
|
-
id, tenant_id, provider, client_name, redirect_uris_json, product_client_id, status, created_at, updated_at
|
|
732
|
+
id, tenant_id, provider, client_name, redirect_uris_json, application_type, product_client_id, status, created_at, updated_at
|
|
725
733
|
)
|
|
726
|
-
VALUES (?, ?, ?, ?, ?, ?, 'active', ?, ?)
|
|
727
|
-
`).run(clientId, this.tenantId, provider, clientName, JSON.stringify(redirectUris), productClientId, now, now);
|
|
734
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, 'active', ?, ?)
|
|
735
|
+
`).run(clientId, this.tenantId, provider, clientName, JSON.stringify(redirectUris), applicationType, productClientId, now, now);
|
|
728
736
|
const row = this.getMcpOAuthClientRow(clientId, provider);
|
|
729
737
|
if (!row) {
|
|
730
738
|
throw new TaskforceRuleError('Failed to register MCP OAuth client', 500, 'MCP_OAUTH_CLIENT_CREATE_FAILED');
|
package/dist/core/Taskforce.d.ts
CHANGED
|
@@ -821,6 +821,7 @@ export interface McpOAuthClientRecord {
|
|
|
821
821
|
id: string;
|
|
822
822
|
provider: McpOAuthProvider;
|
|
823
823
|
clientName: string;
|
|
824
|
+
applicationType: 'native' | 'web';
|
|
824
825
|
productClientId?: string | null;
|
|
825
826
|
redirectUris: string[];
|
|
826
827
|
status: McpOAuthClientStatus;
|
|
@@ -973,7 +974,9 @@ export interface TaskforceSyncCapable {
|
|
|
973
974
|
upsertInitiativeForSync(initiative: InitiativeItem, workspaceId?: string | null): InitiativeItem;
|
|
974
975
|
upsertWorkstreamForSync(workstream: WorkstreamItem, workspaceId?: string | null): WorkstreamItem;
|
|
975
976
|
upsertDocumentReviewSessionForSync(session: DocumentReviewSessionRecord): DocumentReviewSessionRecord;
|
|
976
|
-
upsertDocumentReviewCommentForSync(comment: DocumentReviewCommentSyncRecord
|
|
977
|
+
upsertDocumentReviewCommentForSync(comment: DocumentReviewCommentSyncRecord, options?: {
|
|
978
|
+
allowAuthoritativeThreadReconciliation?: boolean;
|
|
979
|
+
}): DocumentReviewCommentSyncRecord;
|
|
977
980
|
upsertAnnotatedAttachmentSessionForSync(session: AnnotatedAttachmentSessionRecord): AnnotatedAttachmentSessionRecord;
|
|
978
981
|
upsertTaskEventForSync(event: TaskEvent): TaskEvent;
|
|
979
982
|
upsertEntityEventForSync(event: EntityEvent, options?: {
|
|
@@ -1786,7 +1789,9 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
1786
1789
|
includeDeleted?: boolean;
|
|
1787
1790
|
}): AnnotatedAttachmentSessionRecord[];
|
|
1788
1791
|
upsertDocumentReviewSessionForSync(session: DocumentReviewSessionRecord): DocumentReviewSessionRecord;
|
|
1789
|
-
upsertDocumentReviewCommentForSync(comment: Omit<DocumentReviewCommentSyncRecord, 'parentCommentId' | 'threadStatus' | 'threadStatusUpdatedAt' | 'resolvedAt' | 'resolvedByActorId'> & Partial<Pick<DocumentReviewCommentSyncRecord, 'parentCommentId' | 'threadStatus' | 'threadStatusUpdatedAt' | 'resolvedAt' | 'resolvedByActorId'
|
|
1792
|
+
upsertDocumentReviewCommentForSync(comment: Omit<DocumentReviewCommentSyncRecord, 'parentCommentId' | 'threadStatus' | 'threadStatusUpdatedAt' | 'resolvedAt' | 'resolvedByActorId'> & Partial<Pick<DocumentReviewCommentSyncRecord, 'parentCommentId' | 'threadStatus' | 'threadStatusUpdatedAt' | 'resolvedAt' | 'resolvedByActorId'>>, options?: {
|
|
1793
|
+
allowAuthoritativeThreadReconciliation?: boolean;
|
|
1794
|
+
}): DocumentReviewCommentSyncRecord;
|
|
1790
1795
|
upsertAnnotatedAttachmentSessionForSync(session: AnnotatedAttachmentSessionRecord): AnnotatedAttachmentSessionRecord;
|
|
1791
1796
|
upsertTaskEventForSync(event: TaskEvent): TaskEvent;
|
|
1792
1797
|
upsertEntityEventForSync(event: EntityEvent, options?: {
|
|
@@ -2241,6 +2246,7 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
2241
2246
|
provider: McpOAuthProvider | string;
|
|
2242
2247
|
clientName: string;
|
|
2243
2248
|
redirectUris: unknown;
|
|
2249
|
+
applicationType?: unknown;
|
|
2244
2250
|
productClientId?: string | null;
|
|
2245
2251
|
}): McpOAuthClientRecord;
|
|
2246
2252
|
getMcpOAuthClient(clientId: string, provider?: McpOAuthProvider | string | null): McpOAuthClientRecord | null;
|
|
@@ -2456,7 +2462,9 @@ export declare class TaskforceCore implements TaskforceSyncCapable {
|
|
|
2456
2462
|
notifyDurableWorkflowRuntimeCommitted(workspaceId: string, _executionId: string): void;
|
|
2457
2463
|
installDurableInitiativeAuthoritativeForSync(initiative: InitiativeItem, workspaceId: string): InitiativeItem;
|
|
2458
2464
|
installDurableWorkstreamAuthoritativeForSync(workstream: WorkstreamItem, workspaceId: string): WorkstreamItem;
|
|
2459
|
-
installDurableTaskCreateAuthoritativeForSync(task: TaskItem, workspaceId: string
|
|
2465
|
+
installDurableTaskCreateAuthoritativeForSync(task: TaskItem, workspaceId: string, options?: {
|
|
2466
|
+
preserveDeletedSuccessor?: boolean;
|
|
2467
|
+
}): TaskItem;
|
|
2460
2468
|
installWorkspaceSyncV3TaskRootAuthoritativeForSync(task: TaskItem, workspaceId: string): TaskItem;
|
|
2461
2469
|
bulkCreateTasks(tasks: Array<Partial<TaskItem>>, options?: {
|
|
2462
2470
|
atomic?: boolean;
|