@taskforcehq/taskforce 0.3.306 → 0.3.307
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 +0 -2
- package/dist/Taskforce.module.css +317 -27
- package/dist/TaskforceCore.js +1 -0
- package/dist/TaskforceCore.test.js +178 -0
- package/dist/components/activity/EntityActivityTimeline.d.ts +34 -0
- package/dist/components/activity/EntityActivityTimeline.js +81 -0
- package/dist/components/context/ContextAttachmentManager.js +15 -3
- package/dist/components/features/AgentsModule.d.ts +5 -1
- package/dist/components/features/AgentsModule.js +142 -39
- package/dist/components/features/AgentsModule.test.js +260 -11
- package/dist/components/features/AnnotatedAttachmentWorkspace.d.ts +3 -1
- package/dist/components/features/AnnotatedAttachmentWorkspace.js +118 -4
- package/dist/components/features/AnnotatedAttachmentWorkspace.test.js +52 -0
- package/dist/components/features/DocumentIndex.d.ts +2 -2
- package/dist/components/features/DocumentIndex.js +3 -4
- package/dist/components/features/DocumentViewer.d.ts +3 -1
- package/dist/components/features/DocumentViewer.js +6 -5
- package/dist/components/features/DocumentWorkspace.d.ts +10 -2
- package/dist/components/features/DocumentWorkspace.js +15 -4
- package/dist/components/features/DocumentWorkspace.test.js +20 -3
- package/dist/components/features/TaskSettings.d.ts +2 -0
- package/dist/components/features/TaskSettings.js +83 -78
- package/dist/components/features/TaskSettings.test.js +171 -82
- package/dist/components/task/TaskActionHeader.js +11 -5
- package/dist/components/task/TaskActionHeader.test.js +10 -0
- package/dist/components/task/TaskCard.js +6 -2
- package/dist/components/task/TaskContextUpload.test.js +3 -3
- package/dist/components/task/TaskForm.js +14 -64
- package/dist/components/task/TaskKanban.js +0 -8
- package/dist/components/task/TaskList.js +5 -1
- package/dist/components/task/TaskList.test.js +4 -0
- package/dist/components/ui/HierarchyActionBadge.d.ts +10 -0
- package/dist/components/ui/HierarchyActionBadge.js +5 -0
- package/dist/components/views/AppShellHeader.d.ts +3 -1
- package/dist/components/views/AppShellHeader.js +27 -11
- package/dist/components/views/AppShellHeader.test.js +18 -1
- package/dist/components/views/PlanComparisonPage.js +9 -1
- package/dist/components/views/PlanComparisonPage.test.js +19 -0
- package/dist/components/views/PlansPage.js +1 -1
- package/dist/components/views/StandaloneLayout.js +256 -69
- package/dist/components/views/WidgetView.js +10 -8
- package/dist/components/views/panels/PlanningDrawer.d.ts +19 -6
- package/dist/components/views/panels/PlanningDrawer.js +259 -51
- package/dist/components/views/panels/PlanningDrawer.test.js +392 -4
- package/dist/config/envSchema.js +1 -0
- package/dist/core/AiProfileService.d.ts +7 -0
- package/dist/core/AiProfileService.js +460 -35
- package/dist/core/AiProfiles.test.js +648 -2
- package/dist/core/AttachmentLinkService.d.ts +3 -0
- package/dist/core/AttachmentLinkService.js +58 -13
- package/dist/core/GlobalSettingsService.js +12 -0
- package/dist/core/McpTokenService.d.ts +12 -0
- package/dist/core/McpTokenService.js +44 -1
- package/dist/core/PlanningEntities.test.js +366 -4
- package/dist/core/TaskActivityService.d.ts +28 -1
- package/dist/core/TaskActivityService.js +227 -46
- package/dist/core/TaskActivitySync.test.d.ts +1 -0
- package/dist/core/TaskActivitySync.test.js +90 -0
- package/dist/core/TaskAuditTimeline.test.js +96 -0
- package/dist/core/Taskforce.d.ts +190 -26
- package/dist/core/Taskforce.ids.test.js +58 -0
- package/dist/core/Taskforce.js +775 -71
- package/dist/core/Taskforce.mcpAuth.test.js +109 -0
- package/dist/core/types.d.ts +7 -0
- package/dist/hooks/auth/useTaskforceAuthBootstrap.js +2 -1
- package/dist/hooks/bootstrapTimeouts.d.ts +4 -0
- package/dist/hooks/bootstrapTimeouts.js +8 -0
- package/dist/hooks/bootstrapTimeouts.test.d.ts +1 -0
- package/dist/hooks/bootstrapTimeouts.test.js +12 -0
- package/dist/hooks/sync/orchestratorShared.d.ts +3 -3
- package/dist/hooks/sync/orchestratorShared.js +61 -1
- package/dist/hooks/sync/recovery.js +8 -0
- package/dist/hooks/sync/recovery.test.js +5 -5
- package/dist/hooks/sync/transfers.d.ts +2 -1
- package/dist/hooks/sync/transfers.js +36 -9
- package/dist/hooks/sync/transfers.test.js +534 -2
- package/dist/hooks/useFilterSort.js +13 -13
- package/dist/hooks/useFilterSort.test.js +87 -1
- package/dist/hooks/useSyncOrchestrator.js +50 -5
- package/dist/hooks/useTaskforce.d.ts +85 -50
- package/dist/hooks/useTaskforce.js +44 -14
- package/dist/hooks/useTaskforce.runtime-routing.test.js +94 -0
- package/dist/hooks/useTaskforce.sync-behavior.test.js +99 -0
- package/dist/hooks/useWorkspaceSyncController.js +7 -1
- package/dist/hooks/useWorkspaceSyncController.test.js +48 -0
- package/dist/hooks/workspace/useTaskforceWorkspaceBootstrap.js +14 -7
- package/dist/mcp/canonicalAssetHelpers.d.ts +16 -4
- package/dist/mcp/canonicalAssetHelpers.js +18 -10
- package/dist/mcp/canonicalAssetHelpers.test.d.ts +1 -0
- package/dist/mcp/canonicalAssetHelpers.test.js +67 -0
- package/dist/mcp/clientIntegrations.d.ts +1 -0
- package/dist/mcp/clientIntegrations.js +25 -0
- package/dist/mcp/clientIntegrations.test.js +14 -3
- package/dist/mcp/clientProfileDefaults.js +3 -3
- package/dist/mcp/collaborationRegistrar.js +7 -7
- package/dist/mcp/documentAssetRegistrar.js +113 -7
- package/dist/mcp/runtime.d.ts +1 -0
- package/dist/mcp/runtime.js +969 -122
- package/dist/mcp/runtime.test.js +1083 -11
- package/dist/mcp/taskAttachmentTypes.d.ts +1 -1
- package/dist/mcp/taskPlanningRegistrar.js +48 -0
- package/dist/mcp/toolRegistry.d.ts +56 -0
- package/dist/mcp/toolRegistry.js +8 -0
- package/dist/mcp/toolRegistry.test.js +4 -0
- package/dist/migrations/taskSchemaMigrations.js +21 -0
- package/dist/server/aiProfileAvatarAssets.d.ts +49 -0
- package/dist/server/aiProfileAvatarAssets.js +319 -0
- package/dist/server/aiProfileAvatarAssets.test.d.ts +1 -0
- package/dist/server/aiProfileAvatarAssets.test.js +213 -0
- package/dist/server/annotatedAttachmentsRoutes.test.js +66 -7
- package/dist/server/index.js +4 -0
- package/dist/server/index.test.js +6 -1
- package/dist/server/routes/admin.js +187 -9
- package/dist/server/routes/annotatedAttachments.js +51 -0
- package/dist/server/routes/auth.d.ts +11 -0
- package/dist/server/routes/auth.js +189 -27
- package/dist/server/routes/authSupport.d.ts +4 -0
- package/dist/server/routes/authSupport.js +12 -1
- package/dist/server/routes/billing.js +11 -0
- package/dist/server/routes/billing.test.js +3 -0
- package/dist/server/routes/documents.js +10 -0
- package/dist/server/routes/shared.js +0 -10
- package/dist/server/routes/sync.integration.test.js +221 -0
- package/dist/server/routes/sync.js +13 -3
- package/dist/server/routes/syncSnapshotRoutes.js +2 -0
- package/dist/server/routes/tasks.js +12 -9
- package/dist/server/routes.js +87 -8
- package/dist/server/routes.test.js +890 -22
- package/dist/shared/aiProfileSurfaceType.d.ts +1 -1
- package/dist/shared/aiProfileSurfaceType.js +10 -8
- package/dist/storage/objectStorage.d.ts +1 -0
- package/dist/storage/objectStorage.js +2 -1
- package/dist/storage/objectStorageClient.d.ts +1 -0
- package/dist/storage/objectStorageClient.js +41 -8
- package/dist/storage/objectStorageClient.test.js +26 -1
- package/dist/storage/objectStorageConfig.js +3 -1
- package/dist/storage/objectStorageConfig.test.js +13 -1
- package/dist/sync/collaborationSyncPayload.js +2 -0
- package/dist/sync/collaborationSyncPayload.test.js +1 -0
- package/dist/sync/collaborationSyncState.d.ts +3 -3
- package/dist/sync/collaborationSyncState.js +19 -12
- package/dist/sync/contentSyncPayload.d.ts +5 -1
- package/dist/sync/contentSyncPayload.js +9 -0
- package/dist/sync/contentSyncPayload.test.js +39 -0
- package/dist/sync/contentSyncState.d.ts +5 -1
- package/dist/sync/contentSyncState.js +231 -37
- package/dist/sync/planningSyncPayload.js +5 -0
- package/dist/sync/planningSyncPayload.test.js +24 -0
- package/dist/sync/syncApplyHandlers.d.ts +4 -4
- package/dist/sync/syncApplyHandlers.js +89 -31
- package/dist/sync/syncApplyHandlers.test.js +184 -4
- package/dist/sync/syncResourceAdapters.d.ts +2 -0
- package/dist/sync/syncResourceAdapters.js +2 -0
- package/dist/sync/syncService.d.ts +1 -0
- package/dist/sync/syncService.js +13 -1
- package/dist/sync/syncService.test.js +39 -0
- package/dist/sync/workspacePullFeed.d.ts +4 -0
- package/dist/sync/workspacePullFeed.js +129 -70
- package/dist/sync/workspacePullFeed.test.js +49 -0
- package/dist/sync/workspaceSyncModel.d.ts +28 -6
- package/dist/sync/workspaceSyncModel.js +159 -45
- package/dist/sync/workspaceSyncModel.test.js +269 -3
- package/dist/types.d.ts +33 -2
- package/dist/ui/assets/AgentsModule-2y82_3DO.js +1 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BTLZZCiQ.js +3 -0
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BVKew-mH.css +1 -0
- package/dist/ui/assets/ContextAttachmentManager-D2epuNnG.js +3 -0
- package/dist/ui/assets/DocumentWorkspace-D4rJKXzd.css +1 -0
- package/dist/ui/assets/DocumentWorkspace-sVa-3Do6.js +252 -0
- package/dist/ui/assets/EntityActivityTimeline-DyqvvhuF.css +1 -0
- package/dist/ui/assets/EntityActivityTimeline-Zods27y_.js +1 -0
- package/dist/ui/assets/{InitiativesModule-9E6ParrY.js → InitiativesModule-D4XseTwW.js} +1 -1
- package/dist/ui/assets/PlansPage-D5AcbO0L.js +1 -0
- package/dist/ui/assets/PlansPage-Dvqsz5zc.css +1 -0
- package/dist/ui/assets/TaskContextUpload-uC5qx4Bv.js +1 -0
- package/dist/ui/assets/TaskSettings-CqvPAsTv.js +9 -0
- package/dist/ui/assets/{WorkflowsModule-BA7jcEpH.js → WorkflowsModule-oOA9bcdK.js} +4 -4
- package/dist/ui/assets/documentReferences-DPZuTgHh.js +1 -0
- package/dist/ui/assets/index-607WPo_X.js +4 -0
- package/dist/ui/assets/index-CYyodXsg.css +1 -0
- package/dist/ui/assets/{vendor-icons-DuEd_65c.js → vendor-icons-B32hGuF2.js} +1 -1
- package/dist/ui/index.html +3 -3
- package/dist/ui/product/agent manager.png +0 -0
- package/dist/ui/product/agent_manager_02.png +0 -0
- package/dist/utils/aiProfileEvents.d.ts +7 -0
- package/dist/utils/aiProfileEvents.js +6 -0
- package/dist/utils/assignees.d.ts +6 -0
- package/dist/utils/assignees.js +8 -1
- package/dist/utils/assignees.test.d.ts +1 -0
- package/dist/utils/assignees.test.js +25 -0
- package/dist/utils/avatarDisplay.d.ts +1 -0
- package/dist/utils/avatarDisplay.js +15 -0
- package/dist/utils/contextFiles.d.ts +3 -1
- package/dist/utils/contextFiles.js +2 -1
- package/dist/utils/taskActivity.d.ts +7 -1
- package/dist/utils/taskActivity.js +63 -1
- package/dist/utils/taskActivity.test.js +23 -1
- package/dist/utils/taskSearch.d.ts +22 -0
- package/dist/utils/taskSearch.js +87 -0
- package/dist/utils/taskSearch.test.d.ts +1 -0
- package/dist/utils/taskSearch.test.js +69 -0
- package/dist/utils/taskUtils.js +10 -3
- package/dist/utils/taskUtils.test.d.ts +1 -0
- package/dist/utils/taskUtils.test.js +19 -0
- package/package.json +1 -1
- package/dist/ui/assets/AgentsModule-DrLawwNl.js +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-BaS2VwIr.css +0 -1
- package/dist/ui/assets/AnnotatedAttachmentWorkspace-Z9_whf7F.js +0 -3
- package/dist/ui/assets/DocumentWorkspace-B03MaSkw.js +0 -252
- package/dist/ui/assets/DocumentWorkspace-Dhdso07p.css +0 -1
- package/dist/ui/assets/PlansPage-CvfnMiWq.css +0 -1
- package/dist/ui/assets/PlansPage-DRXscYxH.js +0 -1
- package/dist/ui/assets/TaskSettings-DZX4jk7e.js +0 -9
- package/dist/ui/assets/index-9eT8e0Lu.css +0 -1
- package/dist/ui/assets/index-C6k75jr8.js +0 -6
|
@@ -4,7 +4,7 @@ import Markdown from 'react-markdown';
|
|
|
4
4
|
import { renderToStaticMarkup } from 'react-dom/server';
|
|
5
5
|
import remarkGfm from 'remark-gfm';
|
|
6
6
|
import rehypeSanitize from 'rehype-sanitize';
|
|
7
|
-
import { Printer, Link, Download, Clock, HardDrive, Tag, Pencil, X, Save, Eye, SplitSquareHorizontal, CheckCircle, AlertCircle, Zap, Trash2, MessageSquareText, Send, Bot, User, RotateCcw, CheckCheck } from 'lucide-react';
|
|
7
|
+
import { Printer, Link, Download, Clock, HardDrive, Tag, Pencil, X, Save, Eye, SplitSquareHorizontal, CheckCircle, AlertCircle, Zap, Trash2, MessageSquareText, Send, Bot, User, RotateCcw, CheckCheck, ArrowLeft } from 'lucide-react';
|
|
8
8
|
import styles from '../../Taskforce.module.css';
|
|
9
9
|
import viewerStyles from './DocumentViewer.module.css';
|
|
10
10
|
import taskFormStyles from '../task/TaskForm.module.css';
|
|
@@ -501,7 +501,7 @@ function toggleNthTaskCheckbox(markdown, targetIndex, nextChecked) {
|
|
|
501
501
|
});
|
|
502
502
|
return updated.join('\n');
|
|
503
503
|
}
|
|
504
|
-
export function DocumentViewer({ doc, taskTitle, tasks = [], apiBaseUrl = '', onSaved, onDeleted, enableTaskGeneration = false }) {
|
|
504
|
+
export function DocumentViewer({ doc, taskTitle, tasks = [], apiBaseUrl = '', onSaved, onDeleted, onBackToTask, enableTaskGeneration = false }) {
|
|
505
505
|
const fallbackReadableName = getReadableDocumentName(doc);
|
|
506
506
|
const [currentDocumentName, setCurrentDocumentName] = useState(fallbackReadableName);
|
|
507
507
|
const readableName = currentDocumentName;
|
|
@@ -554,7 +554,8 @@ export function DocumentViewer({ doc, taskTitle, tasks = [], apiBaseUrl = '', on
|
|
|
554
554
|
const query = attachSearch.trim().toLowerCase();
|
|
555
555
|
if (!query)
|
|
556
556
|
return true;
|
|
557
|
-
return task.title.toLowerCase().includes(query)
|
|
557
|
+
return task.title.toLowerCase().includes(query)
|
|
558
|
+
|| String(task.referenceLabel || '').trim().toLowerCase().includes(query);
|
|
558
559
|
});
|
|
559
560
|
const fetchContent = useCallback(async () => {
|
|
560
561
|
setLoadingContent(true);
|
|
@@ -1276,7 +1277,7 @@ export function DocumentViewer({ doc, taskTitle, tasks = [], apiBaseUrl = '', on
|
|
|
1276
1277
|
return;
|
|
1277
1278
|
setRenameValue(readableName);
|
|
1278
1279
|
setRenaming(true);
|
|
1279
|
-
}, children: readableName })), taskTitle && (_jsx("span", { className: viewerStyles.metaTaskName, title: `From task: ${taskTitle}`, children: taskTitle })), attachmentState === 'unattached' && (_jsx("span", { className: viewerStyles.statusBadge, children: "Unattached" })), doc.updatedAt && (_jsxs("span", { className: viewerStyles.metaDate, title: formatDate(doc.updatedAt), children: [_jsx(Clock, { size: 11 }), formatDate(doc.updatedAt)] })), _jsxs("span", { className: viewerStyles.metaSize, children: [_jsx(HardDrive, { size: 11 }), formatBytes(doc.sizeBytes)] }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedReference, onClick: () => { void handleCopyReference(); }, title: "Copy document reference" })), saveStatus.type === 'saved' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusSaved}`, children: [_jsx(CheckCircle, { size: 11 }), "Saved"] })), saveStatus.type === 'error' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusError}`, title: saveStatus.message, children: [_jsx(AlertCircle, { size: 11 }), saveStatus.message || 'Save failed'] })), isDirty && saveStatus.type !== 'saving' && (_jsx("span", { className: viewerStyles.unsaved, children: "Unsaved changes" }))] }), _jsx("div", { className: viewerStyles.actions, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: viewerStyles.layoutToggle, children: [_jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'editor' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('editor'), title: "Editor only", children: _jsx(Pencil, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'split' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('split'), title: "Split view", children: _jsx(SplitSquareHorizontal, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'preview' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('preview'), title: "Preview only", children: _jsx(Eye, { size: 12 }) })] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCancelEdit, title: "Discard changes", children: [_jsx(X, { size: 13 }), "Cancel"] }), _jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.saveBtn}`, onClick: handleSave, disabled: saveStatus.type === 'saving' || !isDirty, title: "Save (Ctrl+S)", children: [_jsx(Save, { size: 13 }), saveStatus.type === 'saving' ? 'Saving…' : 'Save'] })] })) : (_jsxs(_Fragment, { children: [doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setRenaming((prev) => !prev), title: "Rename document", children: [_jsx(Pencil, { size: 13 }), renaming ? 'Close Rename' : 'Rename'] })), isEditable && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.editBtn}`, onClick: handleEnterEdit, title: "Edit document", children: [_jsx(Pencil, { size: 13 }), "Edit"] })), doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: handleOpenAttachModal, title: "Attach document to a task", children: [_jsx(Link, { size: 13 }), "Attach"] })), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleToggleReviewSidebar, title: showReviewSidebar ? 'Hide review and comments' : 'Show review and comments', children: [_jsx(MessageSquareText, { size: 13 }), showReviewSidebar ? 'Hide Review' : 'Show Review'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCopyLink, title: "Copy link", children: [_jsx(Link, { size: 13 }), copied ? 'Copied!' : 'Copy link'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleDownload, title: "Download .md", children: [_jsx(Download, { size: 13 }), "Download"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handlePrint, title: "Print", children: [_jsx(Printer, { size: 13 }), "Print"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setMonochromeMode((prev) => !prev), title: "Toggle black and white style", children: [_jsx(Eye, { size: 13 }), monochromeMode ? 'Color' : 'B/W'] }), enableTaskGeneration && doc.docType === 'implementation-plan' && !editMode && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.generateBtn}`, onClick: () => setShowGenModal(true), title: "Generate tasks from this plan", children: [_jsx(Zap, { size: 13 }), "Generate Tasks"] })), canDelete && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.dangerBtn}`, onClick: () => { void handleDelete(); }, disabled: deleting, title: attachmentState === 'attached'
|
|
1280
|
+
}, children: readableName })), taskTitle && (_jsx("span", { className: viewerStyles.metaTaskName, title: `From task: ${taskTitle}`, children: taskTitle })), attachmentState === 'unattached' && (_jsx("span", { className: viewerStyles.statusBadge, children: "Unattached" })), doc.updatedAt && (_jsxs("span", { className: viewerStyles.metaDate, title: formatDate(doc.updatedAt), children: [_jsx(Clock, { size: 11 }), formatDate(doc.updatedAt)] })), _jsxs("span", { className: viewerStyles.metaSize, children: [_jsx(HardDrive, { size: 11 }), formatBytes(doc.sizeBytes)] }), documentReferenceLabel && (_jsx(ReferenceBadgeButton, { label: documentReferenceLabel, copied: copiedReference, onClick: () => { void handleCopyReference(); }, title: "Copy document reference" })), saveStatus.type === 'saved' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusSaved}`, children: [_jsx(CheckCircle, { size: 11 }), "Saved"] })), saveStatus.type === 'error' && (_jsxs("span", { className: `${viewerStyles.saveStatus} ${viewerStyles.saveStatusError}`, title: saveStatus.message, children: [_jsx(AlertCircle, { size: 11 }), saveStatus.message || 'Save failed'] })), isDirty && saveStatus.type !== 'saving' && (_jsx("span", { className: viewerStyles.unsaved, children: "Unsaved changes" }))] }), _jsx("div", { className: viewerStyles.actions, children: editMode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: viewerStyles.layoutToggle, children: [_jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'editor' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('editor'), title: "Editor only", children: _jsx(Pencil, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'split' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('split'), title: "Split view", children: _jsx(SplitSquareHorizontal, { size: 12 }) }), _jsx("button", { className: `${viewerStyles.layoutBtn} ${layout === 'preview' ? viewerStyles.layoutBtnActive : ''}`, onClick: () => setLayout('preview'), title: "Preview only", children: _jsx(Eye, { size: 12 }) })] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCancelEdit, title: "Discard changes", children: [_jsx(X, { size: 13 }), "Cancel"] }), _jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.saveBtn}`, onClick: handleSave, disabled: saveStatus.type === 'saving' || !isDirty, title: "Save (Ctrl+S)", children: [_jsx(Save, { size: 13 }), saveStatus.type === 'saving' ? 'Saving…' : 'Save'] })] })) : (_jsxs(_Fragment, { children: [doc.taskId && onBackToTask && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => onBackToTask(doc.taskId), title: "Back to task", "aria-label": "Back to task", children: [_jsx(ArrowLeft, { size: 13 }), "Back to task"] })), doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setRenaming((prev) => !prev), title: "Rename document", children: [_jsx(Pencil, { size: 13 }), renaming ? 'Close Rename' : 'Rename'] })), isEditable && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.editBtn}`, onClick: handleEnterEdit, title: "Edit document", children: [_jsx(Pencil, { size: 13 }), "Edit"] })), doc.assetId && (_jsxs("button", { className: viewerStyles.actionBtn, onClick: handleOpenAttachModal, title: "Attach document to a task", children: [_jsx(Link, { size: 13 }), "Attach"] })), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleToggleReviewSidebar, title: showReviewSidebar ? 'Hide review and comments' : 'Show review and comments', children: [_jsx(MessageSquareText, { size: 13 }), showReviewSidebar ? 'Hide Review' : 'Show Review'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleCopyLink, title: "Copy link", children: [_jsx(Link, { size: 13 }), copied ? 'Copied!' : 'Copy link'] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handleDownload, title: "Download .md", children: [_jsx(Download, { size: 13 }), "Download"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: handlePrint, title: "Print", children: [_jsx(Printer, { size: 13 }), "Print"] }), _jsxs("button", { className: viewerStyles.actionBtn, onClick: () => setMonochromeMode((prev) => !prev), title: "Toggle black and white style", children: [_jsx(Eye, { size: 13 }), monochromeMode ? 'Color' : 'B/W'] }), enableTaskGeneration && doc.docType === 'implementation-plan' && !editMode && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.generateBtn}`, onClick: () => setShowGenModal(true), title: "Generate tasks from this plan", children: [_jsx(Zap, { size: 13 }), "Generate Tasks"] })), canDelete && (_jsxs("button", { className: `${viewerStyles.actionBtn} ${viewerStyles.dangerBtn}`, onClick: () => { void handleDelete(); }, disabled: deleting, title: attachmentState === 'attached'
|
|
1280
1281
|
? 'Remove this document from linked tasks and delete it'
|
|
1281
1282
|
: 'Permanently delete this unattached document', children: [_jsx(Trash2, { size: 13 }), deleting
|
|
1282
1283
|
? 'Deleting…'
|
|
@@ -1310,6 +1311,6 @@ export function DocumentViewer({ doc, taskTitle, tasks = [], apiBaseUrl = '', on
|
|
|
1310
1311
|
}, title: "Attach document", size: "md", footer: (_jsx("button", { className: viewerStyles.actionBtn, onClick: () => setAttachModalOpen(false), disabled: Boolean(attachingTaskId), children: "Close" })), children: _jsxs("div", { className: viewerStyles.attachModalBody, children: [_jsx("p", { className: viewerStyles.attachModalCopy, children: "Attach this document to a task from the document manager. Initiative and workstream targets can be enabled later on the same attachment model." }), _jsx("input", { type: "text", className: styles.input, placeholder: "Search tasks\u2026", value: attachSearch, onChange: (event) => setAttachSearch(event.target.value) }), attachError && (_jsxs("div", { className: viewerStyles.attachModalError, children: [_jsx(AlertCircle, { size: 14 }), _jsx("span", { children: attachError })] })), _jsx("div", { className: `${viewerStyles.attachTaskList} tf-scrollbar`, children: filteredTasks.length === 0 ? (_jsx("div", { className: viewerStyles.attachEmptyState, children: tasks.length === 0 ? 'No tasks are available in this workspace.' : 'No tasks match your search.' })) : (filteredTasks.map((task) => {
|
|
1311
1312
|
const isCurrentPrimaryAttachment = doc.taskId === task.id;
|
|
1312
1313
|
const isBusy = attachingTaskId === task.id;
|
|
1313
|
-
return (_jsxs("button", { type: "button", className: viewerStyles.attachTaskRow, disabled: isBusy, onClick: () => { void handleAttachToTask(task.id); }, children: [_jsx("span", { className: viewerStyles.attachTaskTitle, children: task.title }), _jsx("span", { className: viewerStyles.attachTaskMeta, children: isBusy ? 'Attaching…' : isCurrentPrimaryAttachment ? 'Attached' : 'Attach' })] }, task.id));
|
|
1314
|
+
return (_jsxs("button", { type: "button", className: viewerStyles.attachTaskRow, disabled: isBusy, onClick: () => { void handleAttachToTask(task.id); }, children: [_jsxs("span", { className: viewerStyles.attachTaskInfo, children: [_jsx("span", { className: viewerStyles.attachTaskTitle, children: task.title }), task.referenceLabel && (_jsx("span", { className: viewerStyles.attachTaskReference, children: task.referenceLabel }))] }), _jsx("span", { className: viewerStyles.attachTaskMeta, children: isBusy ? 'Attaching…' : isCurrentPrimaryAttachment ? 'Attached' : 'Attach' })] }, task.id));
|
|
1314
1315
|
})) })] }) }), !editMode && (_jsx("div", { className: viewerStyles.printHint, children: "For the cleanest output, disable browser Headers and footers in the print dialog." }))] }));
|
|
1315
1316
|
}
|
|
@@ -7,6 +7,7 @@ interface Props {
|
|
|
7
7
|
tasks: Array<{
|
|
8
8
|
id: string;
|
|
9
9
|
title: string;
|
|
10
|
+
referenceLabel?: string | null;
|
|
10
11
|
}>;
|
|
11
12
|
apiBaseUrl?: string;
|
|
12
13
|
documents: DocMeta[];
|
|
@@ -15,16 +16,20 @@ interface Props {
|
|
|
15
16
|
selectedDoc: DocMeta | null;
|
|
16
17
|
searchQuery: string;
|
|
17
18
|
hasActiveFilters: boolean;
|
|
19
|
+
documentTrayOpen?: boolean;
|
|
18
20
|
onSearchChange: (value: string) => void;
|
|
19
21
|
onSelectDoc: (doc: DocMeta | null) => void;
|
|
20
22
|
onRefresh: () => Promise<void> | void;
|
|
23
|
+
onCloseDocumentTray?: () => void;
|
|
21
24
|
onDeleteSelected: () => Promise<void> | void;
|
|
25
|
+
onBackToTask?: (taskId: string) => void;
|
|
22
26
|
enableTaskGeneration?: boolean;
|
|
23
27
|
}
|
|
24
28
|
interface ShellProps {
|
|
25
29
|
tasks: Array<{
|
|
26
30
|
id: string;
|
|
27
31
|
title: string;
|
|
32
|
+
referenceLabel?: string | null;
|
|
28
33
|
}>;
|
|
29
34
|
runtimeMode?: 'local' | 'cloud';
|
|
30
35
|
apiBaseUrl?: string;
|
|
@@ -33,10 +38,13 @@ interface ShellProps {
|
|
|
33
38
|
attachmentFilters?: DocumentAttachmentFilter[];
|
|
34
39
|
onTypeFiltersChange?: (values: DocumentTypeFilter[]) => void;
|
|
35
40
|
onAttachmentFiltersChange?: (values: DocumentAttachmentFilter[]) => void;
|
|
41
|
+
documentTrayOpen?: boolean;
|
|
42
|
+
onCloseDocumentTray?: () => void;
|
|
36
43
|
requestedDocPath?: string | null;
|
|
37
44
|
requestedDocAssetId?: string | null;
|
|
38
45
|
onRequestedDocHandled?: () => void;
|
|
46
|
+
onBackToTask?: (taskId: string) => void;
|
|
39
47
|
enableTaskGeneration?: boolean;
|
|
40
48
|
}
|
|
41
|
-
export declare function DocumentWorkspace({ tasks, apiBaseUrl, documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, onSearchChange, onSelectDoc, onRefresh, onDeleteSelected, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
|
|
42
|
-
export declare function DocumentWorkspaceShell({ tasks, runtimeMode, apiBaseUrl, cloudAuthBaseUrl, typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, requestedDocPath, requestedDocAssetId, onRequestedDocHandled, enableTaskGeneration }: ShellProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export declare function DocumentWorkspace({ tasks, apiBaseUrl, documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, documentTrayOpen, onSearchChange, onSelectDoc, onRefresh, onCloseDocumentTray, onDeleteSelected, onBackToTask, enableTaskGeneration }: Props): import("react/jsx-runtime").JSX.Element;
|
|
50
|
+
export declare function DocumentWorkspaceShell({ tasks, runtimeMode, apiBaseUrl, cloudAuthBaseUrl, typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, documentTrayOpen, onCloseDocumentTray, requestedDocPath, requestedDocAssetId, onRequestedDocHandled, onBackToTask, enableTaskGeneration }: ShellProps): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,14 +1,17 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import React from 'react';
|
|
2
3
|
import styles from '../../Taskforce.module.css';
|
|
3
4
|
import { DocumentIndex } from './DocumentIndex';
|
|
4
5
|
import { DocumentViewer } from './DocumentViewer';
|
|
5
6
|
import { useDocumentWorkspaceController } from './useDocumentWorkspaceController';
|
|
6
7
|
export { DOCUMENT_TYPE_FILTER_OPTIONS, DOCUMENT_ATTACHMENT_FILTER_OPTIONS } from './documentWorkspaceFilters';
|
|
7
|
-
export function DocumentWorkspace({ tasks, apiBaseUrl = '', documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, onSearchChange, onSelectDoc, onRefresh, onDeleteSelected, enableTaskGeneration = false }) {
|
|
8
|
+
export function DocumentWorkspace({ tasks, apiBaseUrl = '', documents, loading, error, selectedDoc, searchQuery, hasActiveFilters, documentTrayOpen, onSearchChange, onSelectDoc, onRefresh, onCloseDocumentTray, onDeleteSelected, onBackToTask, enableTaskGeneration = false }) {
|
|
8
9
|
const taskById = new Map(tasks.map(t => [t.id, t]));
|
|
9
|
-
|
|
10
|
+
const useTrayIndex = typeof documentTrayOpen === 'boolean';
|
|
11
|
+
const documentIndex = (_jsx(DocumentIndex, { documents: documents, selectedDocId: selectedDoc?.id ?? null, loading: loading, error: error, searchQuery: searchQuery, onSearchChange: onSearchChange, onSelect: (doc) => onSelectDoc(doc), onCloseTray: useTrayIndex ? onCloseDocumentTray : undefined, taskById: taskById, hasActiveFilters: hasActiveFilters }));
|
|
12
|
+
return (_jsxs("div", { className: `${styles.docWorkspace} ${useTrayIndex ? styles.docWorkspaceTrayMode : ''} ${useTrayIndex && documentTrayOpen ? styles.docWorkspaceTrayOpen : ''}`.trim(), children: [useTrayIndex ? (_jsx("div", { className: `${styles.docTrayPanel} ${documentTrayOpen ? styles.docTrayPanelOpen : ''}`.trim(), "aria-hidden": !documentTrayOpen, children: documentIndex })) : (_jsx("div", { className: styles.docIndexPanel, children: documentIndex })), _jsx("div", { className: styles.docViewerPanel, children: selectedDoc ? (_jsx(DocumentViewer, { doc: selectedDoc, taskTitle: selectedDoc.taskId ? (taskById.get(selectedDoc.taskId)?.title ?? null) : null, tasks: tasks, apiBaseUrl: apiBaseUrl, onSaved: (_updatedAt) => onRefresh(), onDeleted: () => void onDeleteSelected(), onBackToTask: onBackToTask, enableTaskGeneration: enableTaskGeneration })) : !loading && (_jsx("div", { className: styles.docViewerEmpty, children: _jsx("p", { className: "tf-empty-copy", children: documents.length === 0 ? 'No documents match your current filters.' : 'Select a document to view it.' }) })) })] }));
|
|
10
13
|
}
|
|
11
|
-
export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, requestedDocPath = null, requestedDocAssetId = null, onRequestedDocHandled, enableTaskGeneration = false }) {
|
|
14
|
+
export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUrl = '', cloudAuthBaseUrl = '', typeFilters: controlledTypeFilters, attachmentFilters: controlledAttachmentFilters, onTypeFiltersChange, onAttachmentFiltersChange, documentTrayOpen, onCloseDocumentTray, requestedDocPath = null, requestedDocAssetId = null, onRequestedDocHandled, onBackToTask, enableTaskGeneration = false }) {
|
|
12
15
|
const { documentApiBaseUrl, filteredDocs, loading, error, selectedDoc, searchQuery, hasActiveFilters, setSearchQuery, fetchDocuments, selectDoc, handleDeleteSelected, } = useDocumentWorkspaceController({
|
|
13
16
|
runtimeMode,
|
|
14
17
|
apiBaseUrl,
|
|
@@ -21,5 +24,13 @@ export function DocumentWorkspaceShell({ tasks, runtimeMode = 'local', apiBaseUr
|
|
|
21
24
|
requestedDocAssetId,
|
|
22
25
|
onRequestedDocHandled,
|
|
23
26
|
});
|
|
24
|
-
|
|
27
|
+
const previousDocumentTrayOpenRef = React.useRef(documentTrayOpen);
|
|
28
|
+
React.useEffect(() => {
|
|
29
|
+
const wasOpen = previousDocumentTrayOpenRef.current === true;
|
|
30
|
+
previousDocumentTrayOpenRef.current = documentTrayOpen;
|
|
31
|
+
if (documentTrayOpen !== true || wasOpen)
|
|
32
|
+
return;
|
|
33
|
+
void fetchDocuments();
|
|
34
|
+
}, [documentTrayOpen, fetchDocuments]);
|
|
35
|
+
return (_jsx(DocumentWorkspace, { tasks: tasks, apiBaseUrl: documentApiBaseUrl, documents: filteredDocs, loading: loading, error: error, selectedDoc: selectedDoc, searchQuery: searchQuery, hasActiveFilters: hasActiveFilters, documentTrayOpen: documentTrayOpen, onSearchChange: setSearchQuery, onSelectDoc: selectDoc, onRefresh: fetchDocuments, onCloseDocumentTray: onCloseDocumentTray, onDeleteSelected: handleDeleteSelected, onBackToTask: onBackToTask, enableTaskGeneration: enableTaskGeneration }));
|
|
25
36
|
}
|
|
@@ -45,7 +45,7 @@ function createEmptyReviewSessionsResponse() {
|
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
47
|
function renderDocumentWorkspace(props) {
|
|
48
|
-
return render(_jsx(DocumentWorkspace, { tasks: props.tasks ?? [], documents: props.documents, loading: false, error: null, selectedDoc: props.selectedDoc ?? null, searchQuery: props.searchQuery ?? '', hasActiveFilters: props.hasActiveFilters ?? false, onSearchChange: props.onSearchChange ?? (() => { }), onSelectDoc: props.onSelectDoc ?? (() => { }), onRefresh: props.onRefresh ?? (() => { }), onDeleteSelected: props.onDeleteSelected ?? (() => { }) }));
|
|
48
|
+
return render(_jsx(DocumentWorkspace, { tasks: props.tasks ?? [], documents: props.documents, loading: false, error: null, selectedDoc: props.selectedDoc ?? null, searchQuery: props.searchQuery ?? '', hasActiveFilters: props.hasActiveFilters ?? false, documentTrayOpen: props.documentTrayOpen, onSearchChange: props.onSearchChange ?? (() => { }), onSelectDoc: props.onSelectDoc ?? (() => { }), onRefresh: props.onRefresh ?? (() => { }), onDeleteSelected: props.onDeleteSelected ?? (() => { }) }));
|
|
49
49
|
}
|
|
50
50
|
describe('Document workspace management', () => {
|
|
51
51
|
beforeEach(() => {
|
|
@@ -97,6 +97,16 @@ describe('Document workspace management', () => {
|
|
|
97
97
|
expect(screen.queryByRole('button', { name: /Attached notes/i })).not.toBeInTheDocument();
|
|
98
98
|
expect(screen.getByText('Unattached')).toBeInTheDocument();
|
|
99
99
|
});
|
|
100
|
+
it('can render the document index as a collapsible tray', () => {
|
|
101
|
+
const docs = [createDoc({ id: 'tray-doc', displayName: 'Tray Document' })];
|
|
102
|
+
const { container, rerender } = renderDocumentWorkspace({
|
|
103
|
+
documents: docs,
|
|
104
|
+
documentTrayOpen: true,
|
|
105
|
+
});
|
|
106
|
+
expect(container.querySelector('[aria-hidden="false"]')).toHaveTextContent('Tray Document');
|
|
107
|
+
rerender(_jsx(DocumentWorkspace, { tasks: [], documents: docs, loading: false, error: null, selectedDoc: null, searchQuery: "", hasActiveFilters: false, documentTrayOpen: false, onSearchChange: () => { }, onSelectDoc: () => { }, onRefresh: () => { }, onDeleteSelected: () => { } }));
|
|
108
|
+
expect(container.querySelector('[aria-hidden="true"]')).toHaveTextContent('Tray Document');
|
|
109
|
+
});
|
|
100
110
|
it('keeps document search in the index without duplicate filter controls', () => {
|
|
101
111
|
renderDocumentWorkspace({ documents: [createDoc()] });
|
|
102
112
|
expect(screen.getByPlaceholderText('Search documents…')).toBeInTheDocument();
|
|
@@ -164,12 +174,19 @@ describe('Document workspace management', () => {
|
|
|
164
174
|
attachmentCount: 0,
|
|
165
175
|
attachmentState: 'unattached',
|
|
166
176
|
}), taskTitle: null, tasks: [
|
|
167
|
-
{ id: 'task-1', title: 'First attach target' },
|
|
168
|
-
{ id: 'task-2', title: 'Second attach target' },
|
|
177
|
+
{ id: 'task-1', title: 'First attach target', referenceLabel: 'T-11' },
|
|
178
|
+
{ id: 'task-2', title: 'Second attach target', referenceLabel: 'T-12' },
|
|
169
179
|
], onSaved: onSaved }));
|
|
170
180
|
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenCalledWith('/api/taskforce/documents/asset-doc-1/content', { credentials: 'same-origin' }, ''));
|
|
171
181
|
await user.click(screen.getByRole('button', { name: /^Attach$/i }));
|
|
172
182
|
expect(screen.getByText(/Attach this document to a task from the document manager/i)).toBeInTheDocument();
|
|
183
|
+
expect(screen.getByText('T-11')).toBeInTheDocument();
|
|
184
|
+
expect(screen.getByText('T-12')).toBeInTheDocument();
|
|
185
|
+
await user.clear(screen.getByPlaceholderText('Search tasks…'));
|
|
186
|
+
await user.type(screen.getByPlaceholderText('Search tasks…'), 'T-12');
|
|
187
|
+
expect(screen.queryByRole('button', { name: /First attach target/i })).not.toBeInTheDocument();
|
|
188
|
+
expect(screen.getByRole('button', { name: /Second attach target/i })).toBeInTheDocument();
|
|
189
|
+
await user.clear(screen.getByPlaceholderText('Search tasks…'));
|
|
173
190
|
await user.type(screen.getByPlaceholderText('Search tasks…'), 'Second');
|
|
174
191
|
await user.click(screen.getByRole('button', { name: /Second attach target/i }));
|
|
175
192
|
await waitFor(() => expect(fetchTaskforceApi).toHaveBeenLastCalledWith('/api/taskforce/documents/asset-doc-attach/attach', expect.objectContaining({
|
|
@@ -208,11 +208,13 @@ export interface TaskSettingsModel {
|
|
|
208
208
|
cleanupWarnings?: string[];
|
|
209
209
|
}>;
|
|
210
210
|
onMcpHostRootChange?: (value: string) => void;
|
|
211
|
+
onOpenCloudAuth?: (mode: 'login' | 'register') => void;
|
|
211
212
|
isAuthenticated?: boolean;
|
|
212
213
|
}
|
|
213
214
|
interface TaskSettingsProps {
|
|
214
215
|
settingsModel: TaskSettingsModel;
|
|
215
216
|
onSectionChange?: (section: any) => void;
|
|
217
|
+
renderMode?: 'full' | 'mcp-only';
|
|
216
218
|
}
|
|
217
219
|
export declare function TaskSettings(props: TaskSettingsProps): import("react/jsx-runtime").JSX.Element;
|
|
218
220
|
export {};
|