@parhelia/core 0.1.12477 → 0.1.12479
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config/config.js +22 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/types.d.ts +2 -0
- package/dist/config/types.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +20 -4
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/ContentTree.js +19 -7
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/PictureCropper.js +45 -41
- package/dist/editor/PictureCropper.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.js +97 -8
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AgentTerminalStatusBar.js +65 -0
- package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
- package/dist/editor/ai/Agents.js +19 -0
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.js +5 -0
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContentInspectorPopover.d.ts +1 -0
- package/dist/editor/ai/ContentInspectorPopover.js +22 -8
- package/dist/editor/ai/ContentInspectorPopover.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +2 -0
- package/dist/editor/ai/ToolCallDisplay.js +54 -11
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/dialogs/AgentDialogHandler.js +32 -3
- package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js +55 -20
- package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
- package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +7 -4
- package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
- package/dist/editor/ai/types.d.ts +2 -0
- package/dist/editor/client/EditorShell.js +55 -10
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +2 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.d.ts +0 -2
- package/dist/editor/client/ui/EditorChrome.js +2 -13
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +8 -1
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
- package/dist/editor/page-viewer/PageViewer.js +23 -4
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +3 -4
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +27 -0
- package/dist/editor/services/agentService.js +11 -2
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.js +54 -3
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/serviceHelper.js +5 -2
- package/dist/editor/services/serviceHelper.js.map +1 -1
- package/dist/editor/settings/About.js +40 -4
- package/dist/editor/settings/About.js.map +1 -1
- package/dist/editor/settings/panels/PersistentLogsPanel.d.ts +2 -0
- package/dist/editor/settings/panels/PersistentLogsPanel.js +244 -0
- package/dist/editor/settings/panels/PersistentLogsPanel.js.map +1 -0
- package/dist/editor/settings/panels/ProjectTemplateAgentPanel.d.ts +7 -1
- package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js +3 -3
- package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js +165 -84
- package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
- package/dist/editor/settings/panels/index.d.ts +1 -0
- package/dist/editor/settings/panels/index.js +1 -0
- package/dist/editor/settings/panels/index.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
- package/dist/editor/sidebar/ComponentTree.js +23 -15
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/NavigationPanelItem.js +1 -1
- package/dist/editor/sidebar/NavigationPanelItem.js.map +1 -1
- package/dist/editor/sidebar/WorkspaceButton.js +1 -1
- package/dist/editor/sidebar/WorkspaceButton.js.map +1 -1
- package/dist/editor/tree-indicators/GutterColumns.js +24 -4
- package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
- package/dist/editor/tree-indicators/types.d.ts +10 -0
- package/dist/editor/ui/Splitter.d.ts +1 -0
- package/dist/editor/ui/Splitter.js +7 -1
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/editor/utils.js +16 -4
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/views/CompareView.d.ts +3 -1
- package/dist/editor/views/CompareView.js +3 -3
- package/dist/editor/views/CompareView.js.map +1 -1
- package/dist/editor/views/EditorSlot.js +7 -6
- package/dist/editor/views/EditorSlot.js.map +1 -1
- package/dist/editor/views/SingleEditView.d.ts +2 -1
- package/dist/editor/views/SingleEditView.js +8 -8
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/licensing/LicenseContext.js +40 -4
- package/dist/licensing/LicenseContext.js.map +1 -1
- package/dist/licensing/LicenseOverlay.js +1 -1
- package/dist/licensing/LicenseOverlay.js.map +1 -1
- package/dist/licensing/types.d.ts +3 -1
- package/dist/licensing/types.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/task-board/TaskBoardWorkspace.js +165 -354
- package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
- package/dist/task-board/assigneeDisplay.js +1 -3
- package/dist/task-board/assigneeDisplay.js.map +1 -1
- package/dist/task-board/components/CreateProjectDialog.js +2 -1
- package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
- package/dist/task-board/components/ProjectDashboard.js +2 -1
- package/dist/task-board/components/ProjectDashboard.js.map +1 -1
- package/dist/task-board/components/ProjectExecutionUserPicker.d.ts +14 -0
- package/dist/task-board/components/ProjectExecutionUserPicker.js +65 -0
- package/dist/task-board/components/ProjectExecutionUserPicker.js.map +1 -0
- package/dist/task-board/components/ProjectSettingsDialog.d.ts +1 -0
- package/dist/task-board/components/ProjectSettingsDialog.js +146 -12
- package/dist/task-board/components/ProjectSettingsDialog.js.map +1 -1
- package/dist/task-board/components/TaskAgentPanel.d.ts +1 -0
- package/dist/task-board/components/TaskAgentPanel.js +2 -2
- package/dist/task-board/components/TaskAgentPanel.js.map +1 -1
- package/dist/task-board/components/TaskAssigneePicker.js +2 -2
- package/dist/task-board/components/TaskAssigneePicker.js.map +1 -1
- package/dist/task-board/components/TaskBoardMyTasksSidebar.js +1 -1
- package/dist/task-board/components/TaskBoardMyTasksSidebar.js.map +1 -1
- package/dist/task-board/components/TaskDetailDialog.d.ts +1 -0
- package/dist/task-board/components/TaskDetailDialog.js +2 -2
- package/dist/task-board/components/TaskDetailDialog.js.map +1 -1
- package/dist/task-board/components/TaskDetailPanel.d.ts +1 -0
- package/dist/task-board/components/TaskDetailPanel.js +23 -8
- package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
- package/dist/task-board/components/TaskRow.js +3 -2
- package/dist/task-board/components/TaskRow.js.map +1 -1
- package/dist/task-board/components/TaskboardPersistentLogPanel.d.ts +11 -0
- package/dist/task-board/components/TaskboardPersistentLogPanel.js +141 -0
- package/dist/task-board/components/TaskboardPersistentLogPanel.js.map +1 -0
- package/dist/task-board/components/WizardTaskDetailsPanel.js +2 -1
- package/dist/task-board/components/WizardTaskDetailsPanel.js.map +1 -1
- package/dist/task-board/persistentLogCopy.d.ts +7 -0
- package/dist/task-board/persistentLogCopy.js +80 -0
- package/dist/task-board/persistentLogCopy.js.map +1 -0
- package/dist/task-board/persistentLogLabels.d.ts +38 -0
- package/dist/task-board/persistentLogLabels.js +189 -0
- package/dist/task-board/persistentLogLabels.js.map +1 -0
- package/dist/task-board/services/taskService.d.ts +17 -1
- package/dist/task-board/services/taskService.js +56 -0
- package/dist/task-board/services/taskService.js.map +1 -1
- package/dist/task-board/taskExecutionRecords.js +2 -0
- package/dist/task-board/taskExecutionRecords.js.map +1 -1
- package/dist/task-board/types.d.ts +78 -1
- package/dist/task-board/useTaskBoardAgentPanelState.d.ts +34 -0
- package/dist/task-board/useTaskBoardAgentPanelState.js +283 -0
- package/dist/task-board/useTaskBoardAgentPanelState.js.map +1 -0
- package/dist/task-board/utils/taskDependencyOrdering.d.ts +6 -0
- package/dist/task-board/utils/taskDependencyOrdering.js +138 -1
- package/dist/task-board/utils/taskDependencyOrdering.js.map +1 -1
- package/dist/task-board/views/DependencyGraphView.d.ts +5 -2
- package/dist/task-board/views/DependencyGraphView.js +261 -69
- package/dist/task-board/views/DependencyGraphView.js.map +1 -1
- package/dist/task-board/views/KanbanView.js +8 -1
- package/dist/task-board/views/KanbanView.js.map +1 -1
- package/dist/task-board/views/ListView.js +1 -1
- package/dist/task-board/views/ListView.js.map +1 -1
- package/dist/task-board/views/WizardView.js +30 -24
- package/dist/task-board/views/WizardView.js.map +1 -1
- package/dist/tour/Tour.js +0 -47
- package/dist/tour/Tour.js.map +1 -1
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
|
|
3
|
-
import { AlertCircle, ChevronDown, CopyPlus, CornerDownRight, GitBranch, Link2, Plus, RefreshCw, Search, Settings2, Shield, Trash2, X, } from "lucide-react";
|
|
3
|
+
import { AlertCircle, Bot, ChevronDown, ChevronLeft, CopyPlus, CornerDownRight, GitBranch, Link2, Plus, RefreshCw, Search, Settings2, Shield, Trash2, X, } from "lucide-react";
|
|
4
4
|
import { toast } from "sonner";
|
|
5
5
|
import { Button } from "../../../components/ui/button";
|
|
6
6
|
import { Dialog, DialogContent, DialogFooter, } from "../../../components/ui/dialog";
|
|
@@ -26,7 +26,7 @@ import { AgentProfileEditorPanel, } from "./AgentProfileEditorPanel";
|
|
|
26
26
|
import { ProjectTemplateAgentPanel } from "./ProjectTemplateAgentPanel";
|
|
27
27
|
import { useSearchParams } from "next/navigation";
|
|
28
28
|
import { ParheliaIconWhite } from "../../ui/ParheliaIconWhite";
|
|
29
|
-
|
|
29
|
+
// Query param for deep-linking the selected project template (settings reload).
|
|
30
30
|
const SELECTED_PROJECT_TEMPLATE_QUERY_PARAM = "projectTemplateId";
|
|
31
31
|
const PRIORITY_OPTIONS = [
|
|
32
32
|
{ value: "", label: "No priority" },
|
|
@@ -45,7 +45,8 @@ function isProjectTemplateItemChange(changes, selectedTemplateId) {
|
|
|
45
45
|
const normalizedSelectedTemplateId = selectedTemplateId?.toLowerCase() ?? null;
|
|
46
46
|
return changes.some((change) => {
|
|
47
47
|
const path = change.item?.path?.trim().toLowerCase();
|
|
48
|
-
if (path &&
|
|
48
|
+
if (path &&
|
|
49
|
+
PROJECT_TEMPLATE_ROOT_PATHS.some((root) => path.startsWith(root))) {
|
|
49
50
|
return true;
|
|
50
51
|
}
|
|
51
52
|
const itemId = change.item?.id?.trim().toLowerCase();
|
|
@@ -74,7 +75,7 @@ function normalizeTaskTemplate(task) {
|
|
|
74
75
|
parentTaskId: rawParentTaskId,
|
|
75
76
|
};
|
|
76
77
|
}
|
|
77
|
-
if (assigneeType === "
|
|
78
|
+
if (assigneeType === "user" || assigneeType === "Role") {
|
|
78
79
|
return {
|
|
79
80
|
...task,
|
|
80
81
|
disabled,
|
|
@@ -93,11 +94,20 @@ function normalizeTaskTemplate(task) {
|
|
|
93
94
|
parentTaskId: rawParentTaskId,
|
|
94
95
|
};
|
|
95
96
|
}
|
|
97
|
+
function normalizeGraphOrientation(orientation) {
|
|
98
|
+
return orientation === "vertical" ? "vertical" : "horizontal";
|
|
99
|
+
}
|
|
96
100
|
function normalizeProjectTemplate(template) {
|
|
97
101
|
return {
|
|
98
102
|
...template,
|
|
99
103
|
hideFromCreateDialog: template.hideFromCreateDialog === true,
|
|
100
104
|
openInWizardMode: template.openInWizardMode === true,
|
|
105
|
+
graphLayout: template.graphLayout
|
|
106
|
+
? {
|
|
107
|
+
...template.graphLayout,
|
|
108
|
+
orientation: normalizeGraphOrientation(template.graphLayout.orientation),
|
|
109
|
+
}
|
|
110
|
+
: null,
|
|
101
111
|
taskTemplates: (template.taskTemplates ?? []).map(normalizeTaskTemplate),
|
|
102
112
|
};
|
|
103
113
|
}
|
|
@@ -150,6 +160,7 @@ function buildUpsertTemplateRequest(template) {
|
|
|
150
160
|
projectTemplateId: template.id,
|
|
151
161
|
nodes: template.graphLayout.nodes ?? [],
|
|
152
162
|
viewport: template.graphLayout.viewport ?? null,
|
|
163
|
+
orientation: normalizeGraphOrientation(template.graphLayout.orientation),
|
|
153
164
|
}
|
|
154
165
|
: null,
|
|
155
166
|
taskTemplates: template.taskTemplates.map((task, index) => {
|
|
@@ -183,6 +194,7 @@ function mergeTemplateResponseWithRequest(serverTemplate, request) {
|
|
|
183
194
|
projectTemplateId: serverTemplate.id,
|
|
184
195
|
nodes: request.graphLayout.nodes ?? [],
|
|
185
196
|
viewport: request.graphLayout.viewport ?? null,
|
|
197
|
+
orientation: normalizeGraphOrientation(request.graphLayout.orientation),
|
|
186
198
|
};
|
|
187
199
|
return normalizeProjectTemplate({
|
|
188
200
|
...serverTemplate,
|
|
@@ -324,6 +336,7 @@ function buildProjectTemplateAgentContext(template, selectedTaskTemplateId) {
|
|
|
324
336
|
}
|
|
325
337
|
export function ProjectTemplatesPanel() {
|
|
326
338
|
const editContext = useEditContext();
|
|
339
|
+
const isMobile = editContext?.isMobile ?? false;
|
|
327
340
|
const searchParams = useSearchParams();
|
|
328
341
|
const [state, setState] = useState("loading");
|
|
329
342
|
const [templates, setTemplates] = useState([]);
|
|
@@ -353,6 +366,9 @@ export function ProjectTemplatesPanel() {
|
|
|
353
366
|
const [templateAgentResetting, setTemplateAgentResetting] = useState(false);
|
|
354
367
|
const [templateAgentError, setTemplateAgentError] = useState(null);
|
|
355
368
|
const [duplicatingAgentProfile, setDuplicatingAgentProfile] = useState(false);
|
|
369
|
+
const [mobileView, setMobileView] = useState("list");
|
|
370
|
+
// Desktop: hide splitter list + detail; show only the assistant column (full width).
|
|
371
|
+
const [desktopAssistantOnly, setDesktopAssistantOnly] = useState(false);
|
|
356
372
|
const [editingAgentProfile, setEditingAgentProfile] = useState(null);
|
|
357
373
|
const autosaveTimeoutRef = useRef(null);
|
|
358
374
|
const lastPersistedSignatureRef = useRef(null);
|
|
@@ -361,6 +377,8 @@ export function ProjectTemplatesPanel() {
|
|
|
361
377
|
const draftTemplateRef = useRef(null);
|
|
362
378
|
const selectedTemplateIdRef = useRef(selectedTemplateId);
|
|
363
379
|
const selectedTaskIdRef = useRef(selectedTaskId);
|
|
380
|
+
const isDirtyRef = useRef(isDirty);
|
|
381
|
+
const savingRef = useRef(saving);
|
|
364
382
|
const templateAgentLoadRunIdRef = useRef(0);
|
|
365
383
|
const externalRefreshTimeoutRef = useRef(null);
|
|
366
384
|
const pendingExternalRefreshRef = useRef(false);
|
|
@@ -405,6 +423,7 @@ export function ProjectTemplatesPanel() {
|
|
|
405
423
|
setSelectedTemplateId(template?.id ?? null);
|
|
406
424
|
setDraftTemplate(clonedTemplate);
|
|
407
425
|
setEditingAgentProfile(null);
|
|
426
|
+
setDesktopAssistantOnly(false);
|
|
408
427
|
setSelectedTaskId(getSelectedTaskIdForTemplate(clonedTemplate, preferredTaskId));
|
|
409
428
|
setIsDirty(false);
|
|
410
429
|
setSaveError(null);
|
|
@@ -418,6 +437,27 @@ export function ProjectTemplatesPanel() {
|
|
|
418
437
|
});
|
|
419
438
|
}
|
|
420
439
|
}, [editContext, searchParams]);
|
|
440
|
+
const handleMobileBackToList = useCallback(() => {
|
|
441
|
+
setMobileView("list");
|
|
442
|
+
selectTemplate(null);
|
|
443
|
+
}, [selectTemplate]);
|
|
444
|
+
const handleMobileBackFromAgent = useCallback(() => {
|
|
445
|
+
setEditingAgentProfile(null);
|
|
446
|
+
setMobileView("detail");
|
|
447
|
+
}, []);
|
|
448
|
+
useEffect(() => {
|
|
449
|
+
if (!isMobile) {
|
|
450
|
+
setMobileView("list");
|
|
451
|
+
}
|
|
452
|
+
else {
|
|
453
|
+
setDesktopAssistantOnly(false);
|
|
454
|
+
}
|
|
455
|
+
}, [isMobile]);
|
|
456
|
+
useEffect(() => {
|
|
457
|
+
if (!isMobile || !editingAgentProfile)
|
|
458
|
+
return;
|
|
459
|
+
setMobileView("agent");
|
|
460
|
+
}, [isMobile, editingAgentProfile]);
|
|
421
461
|
const loadTemplates = useCallback(async (preferredTemplateId) => {
|
|
422
462
|
try {
|
|
423
463
|
setState("loading");
|
|
@@ -549,7 +589,7 @@ export function ProjectTemplatesPanel() {
|
|
|
549
589
|
if (isDirty || saving) {
|
|
550
590
|
return (_jsx("span", { "data-testid": "project-template-save-status", "data-state": "saving", className: "inline-flex min-w-[84px] justify-center rounded bg-amber-100 px-1.5 py-0.5 whitespace-nowrap text-amber-700", children: "Saving..." }));
|
|
551
591
|
}
|
|
552
|
-
return (_jsx("span", { "aria-hidden": "true", "data-testid": "project-template-save-status", "data-state": "saved", className: "inline-flex min-w-[84px] justify-center rounded px-1.5 py-0.5 whitespace-nowrap opacity-0", children: "Saving..." }));
|
|
592
|
+
return (_jsx("span", { "aria-hidden": "true", "data-testid": "project-template-save-status", "data-state": "saved", className: "pointer-events-none inline-flex min-w-[84px] justify-center rounded px-1.5 py-0.5 whitespace-nowrap opacity-0 select-none", children: "Saving..." }));
|
|
553
593
|
}, [isDirty, saveError, saving]);
|
|
554
594
|
const graphTasks = useMemo(() => buildTemplateTaskItems(draftTemplate, aiProfilesById), [aiProfilesById, draftTemplate]);
|
|
555
595
|
const graphDependencies = useMemo(() => buildTemplateDependencies(draftTemplate), [draftTemplate]);
|
|
@@ -579,7 +619,10 @@ export function ProjectTemplatesPanel() {
|
|
|
579
619
|
}, [selectedAgentProfile, selectedAgentProfileId, selectedTask?.title]);
|
|
580
620
|
const handleCloseEditingAgentProfile = useCallback(() => {
|
|
581
621
|
setEditingAgentProfile(null);
|
|
582
|
-
|
|
622
|
+
if (isMobile) {
|
|
623
|
+
setMobileView("detail");
|
|
624
|
+
}
|
|
625
|
+
}, [isMobile]);
|
|
583
626
|
const syncProjectTemplateAgentContext = useCallback(async (agentId, template, selectedTaskTemplateId) => {
|
|
584
627
|
try {
|
|
585
628
|
await updateAgentContext(agentId, buildProjectTemplateAgentContext(template, selectedTaskTemplateId));
|
|
@@ -886,6 +929,12 @@ export function ProjectTemplatesPanel() {
|
|
|
886
929
|
useEffect(() => {
|
|
887
930
|
selectedTaskIdRef.current = selectedTaskId;
|
|
888
931
|
}, [selectedTaskId]);
|
|
932
|
+
useEffect(() => {
|
|
933
|
+
isDirtyRef.current = isDirty;
|
|
934
|
+
}, [isDirty]);
|
|
935
|
+
useEffect(() => {
|
|
936
|
+
savingRef.current = saving;
|
|
937
|
+
}, [saving]);
|
|
889
938
|
const queueExternalTemplateRefresh = useCallback(() => {
|
|
890
939
|
if (saving || isDirty) {
|
|
891
940
|
pendingExternalRefreshRef.current = true;
|
|
@@ -897,9 +946,23 @@ export function ProjectTemplatesPanel() {
|
|
|
897
946
|
}
|
|
898
947
|
externalRefreshTimeoutRef.current = window.setTimeout(() => {
|
|
899
948
|
externalRefreshTimeoutRef.current = null;
|
|
949
|
+
if (savingRef.current || isDirtyRef.current) {
|
|
950
|
+
pendingExternalRefreshRef.current = true;
|
|
951
|
+
return;
|
|
952
|
+
}
|
|
900
953
|
void loadTemplatesRef.current(selectedTemplateIdRef.current);
|
|
901
954
|
}, PROJECT_TEMPLATE_EXTERNAL_REFRESH_DEBOUNCE_MS);
|
|
902
955
|
}, [isDirty, saving]);
|
|
956
|
+
useEffect(() => {
|
|
957
|
+
if (!saving && !isDirty) {
|
|
958
|
+
return;
|
|
959
|
+
}
|
|
960
|
+
if (externalRefreshTimeoutRef.current !== null) {
|
|
961
|
+
window.clearTimeout(externalRefreshTimeoutRef.current);
|
|
962
|
+
externalRefreshTimeoutRef.current = null;
|
|
963
|
+
pendingExternalRefreshRef.current = true;
|
|
964
|
+
}
|
|
965
|
+
}, [isDirty, saving]);
|
|
903
966
|
useEffect(() => {
|
|
904
967
|
if (!editContext?.itemsRepository) {
|
|
905
968
|
return;
|
|
@@ -977,8 +1040,11 @@ export function ProjectTemplatesPanel() {
|
|
|
977
1040
|
editContext?.updateUrl({
|
|
978
1041
|
[SELECTED_PROJECT_TEMPLATE_QUERY_PARAM]: nextTemplate.id,
|
|
979
1042
|
});
|
|
1043
|
+
if (isMobile) {
|
|
1044
|
+
setMobileView("detail");
|
|
1045
|
+
}
|
|
980
1046
|
await persistTemplate(request, signature, { showErrorToast: true });
|
|
981
|
-
}, [editContext, flushPendingSave, persistTemplate, templates]);
|
|
1047
|
+
}, [editContext, flushPendingSave, isMobile, persistTemplate, templates]);
|
|
982
1048
|
const handleDeleteTemplate = useCallback((templateToDelete) => {
|
|
983
1049
|
const template = templateToDelete ?? draftTemplate;
|
|
984
1050
|
if (!template || template.isSystem)
|
|
@@ -1009,8 +1075,7 @@ export function ProjectTemplatesPanel() {
|
|
|
1009
1075
|
}
|
|
1010
1076
|
toast.success("Project template deleted");
|
|
1011
1077
|
const fallbackId = isDeletingSelectedTemplate
|
|
1012
|
-
? templates.find((currentTemplate) => currentTemplate.id !== templateId)
|
|
1013
|
-
?.id ?? null
|
|
1078
|
+
? (templates.find((currentTemplate) => currentTemplate.id !== templateId)?.id ?? null)
|
|
1014
1079
|
: selectedTemplateIdRef.current;
|
|
1015
1080
|
await loadTemplates(fallbackId);
|
|
1016
1081
|
}
|
|
@@ -1041,7 +1106,10 @@ export function ProjectTemplatesPanel() {
|
|
|
1041
1106
|
return;
|
|
1042
1107
|
}
|
|
1043
1108
|
selectTemplate(template);
|
|
1044
|
-
|
|
1109
|
+
if (isMobile) {
|
|
1110
|
+
setMobileView("detail");
|
|
1111
|
+
}
|
|
1112
|
+
}, [flushPendingSave, selectTemplate, isMobile]);
|
|
1045
1113
|
const handleRefreshTemplates = useCallback(async () => {
|
|
1046
1114
|
const canContinue = await flushPendingSave({ showErrorToast: true });
|
|
1047
1115
|
if (!canContinue) {
|
|
@@ -1409,103 +1477,116 @@ export function ProjectTemplatesPanel() {
|
|
|
1409
1477
|
handleDeleteTemplate(template);
|
|
1410
1478
|
}, children: _jsx(Trash2, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) }))] }, template.id));
|
|
1411
1479
|
}) })) })] }) }));
|
|
1412
|
-
const
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1480
|
+
const renderTemplateTaskEditorMain = () => {
|
|
1481
|
+
if (!draftTemplate) {
|
|
1482
|
+
return null;
|
|
1483
|
+
}
|
|
1484
|
+
const graphView = (_jsx("div", { className: "h-full min-h-0 overflow-hidden bg-slate-50/40", "data-testid": "project-template-graph", children: _jsx(DependencyGraphView, { projectId: draftTemplate.id, layoutKey: `project-template:${draftTemplate.id}`, tasks: graphTasks, dependencies: graphDependencies, miniMapWidth: 56, miniMapHeight: 42, showMiniMap: !isMobile, orientation: "horizontal", showOrientationToggle: true, autoLayoutStrategy: "hierarchy", savedLayout: draftTemplate.graphLayout, selectedTaskId: selectedTaskId, selectedDependencyId: selectedDependencyId, onSelectTask: handleSelectTask, onSelectDependency: handleSelectDependencyFromGraph, onClearDependencySelection: handleClearDependencySelection, onAddDependentTaskFromNode: (taskId) => {
|
|
1485
|
+
void handleOpenCreateDependentTaskDialogForTask(taskId);
|
|
1486
|
+
}, onAddChildTaskFromNode: (taskId) => {
|
|
1487
|
+
void handleOpenAddChildTaskDialogForTask(taskId);
|
|
1488
|
+
}, onRemoveTask: removeTaskTemplateById, allowDependencyConnect: true, onCreateDependency: handleCreateDependencyFromGraph, onCreateChildRelationship: handleCreateChildRelationshipFromGraph, canPersistLayout: true, layoutSaveDebounceMs: 0, highlightBlockedTasks: false, showExecutionStateBadges: false, emptyStateTitle: "No task templates yet", emptyStateDescription: "Add task templates to start designing the project flow.", onPersistLayout: async (layout) => {
|
|
1489
|
+
const nextLayout = {
|
|
1490
|
+
...layout,
|
|
1491
|
+
projectTemplateId: draftTemplate.id,
|
|
1492
|
+
};
|
|
1493
|
+
applyDraftChange((currentTemplate) => ({
|
|
1494
|
+
...currentTemplate,
|
|
1495
|
+
graphLayout: nextLayout,
|
|
1496
|
+
}));
|
|
1497
|
+
return nextLayout;
|
|
1498
|
+
} }) }));
|
|
1499
|
+
const taskPane = (_jsx("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", "data-testid": "project-template-task-detail-pane", children: !selectedTask ? (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: _jsx("div", { className: "rounded-lg border border-dashed border-gray-200 bg-gray-50 p-6 text-center text-xs text-gray-500", children: "Select a task node to edit its details." }) })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "shrink-0 border-b border-gray-100 bg-white px-4 pt-4 pb-3", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "text-xs font-semibold text-gray-900", "data-testid": "project-template-selected-task-title", children: selectedTask.title || "Untitled Task" }), selectedTask.disabled ? (_jsx(Badge, { variant: "outline", className: "text-[10px] uppercase", children: "Disabled" })) : null] }), _jsx("div", { className: "text-xs text-gray-500", children: "Configure task details, assignment, and dependencies." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleDeleteSelectedTask, "data-testid": "project-template-remove-task-button", children: [_jsx(Trash2, { className: "h-4 w-4", strokeWidth: 1.5 }), "Remove Task"] })] }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 pt-3 pb-4", children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsx(Label, { className: "text-xs", children: "Task title" }), _jsx(Input, { value: selectedTask.title, onChange: (event) => updateSelectedTask((currentTask) => ({
|
|
1500
|
+
...currentTask,
|
|
1501
|
+
title: event.target.value,
|
|
1502
|
+
})), placeholder: "Task title", className: "text-xs md:text-xs", "data-testid": "project-template-task-title-input" })] }), _jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsx(Label, { className: "text-xs", children: "Description" }), _jsx(Textarea, { className: "text-xs", value: selectedTask.description ?? "", onChange: (event) => updateSelectedTask((currentTask) => ({
|
|
1503
|
+
...currentTask,
|
|
1504
|
+
description: event.target.value,
|
|
1505
|
+
})), rows: 4, placeholder: "Describe what this task should accomplish", "data-testid": "project-template-task-description-input" })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { className: "text-xs", children: "Priority" }), _jsx(Select, { className: "text-xs", value: selectedTask.priority ?? "", onValueChange: (value) => updateSelectedTask((currentTask) => ({
|
|
1506
|
+
...currentTask,
|
|
1507
|
+
priority: value || null,
|
|
1508
|
+
})), options: PRIORITY_OPTIONS, "data-testid": "project-template-task-priority-select" })] }), _jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { className: "text-xs", children: "Default assignee" }), selectedTask.assigneeType === "Agent" ? (_jsxs(Button, { type: "button", size: "sm", variant: "outline", className: "h-7 gap-1 px-2 text-xs", onClick: () => void handleEditAssignedAgentProfile(), disabled: !selectedAgentProfileId, "data-testid": "project-template-task-edit-agent-profile-button", children: [_jsx(Settings2, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), "Edit Profile"] })) : null] }), _jsx(TaskAssigneePicker, { projectTemplateId: draftTemplate.id, assigneeType: selectedTask.assigneeType ?? null, value: selectedTask.assigneeId ?? null, displayValue: selectedTaskAssigneeDisplayValue, onChange: (next) => updateSelectedTask((currentTask) => ({
|
|
1509
|
+
...currentTask,
|
|
1510
|
+
assigneeType: next?.assigneeType ?? null,
|
|
1511
|
+
assigneeId: next?.assigneeId ?? null,
|
|
1512
|
+
agentProfileId: next?.assigneeType === "Agent"
|
|
1513
|
+
? next.assigneeId
|
|
1514
|
+
: null,
|
|
1515
|
+
})), placeholder: "Assign user or agent", buttonClassName: "h-8 w-full justify-between rounded-md bg-white text-xs font-medium", buttonTestId: "project-template-task-assignee-picker" }), profilesError ? (_jsx("div", { className: "text-xs text-amber-700", children: profilesError })) : null] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "rounded-lg border border-gray-200 bg-gray-50 p-3", "data-testid": "project-template-dependencies-section", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800", children: [_jsx(Shield, { className: "h-4 w-4 text-gray-500", strokeWidth: 1.5 }), "Dependencies"] }), _jsxs(Popover, { open: dependencyPickerOpen, onOpenChange: setDependencyPickerOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { type: "button", size: "sm", variant: "outline", className: "h-7 gap-1 px-2", disabled: dependencyAddCandidates.length === 0, "aria-label": "Add dependency", "data-testid": "project-template-add-dependency-button", children: _jsx(Plus, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) }) }), _jsxs(PopoverContent, { align: "end", className: "w-72 p-0", sideOffset: 6, "data-testid": "project-template-dependency-popover", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-2 text-xs font-medium text-gray-700", children: "Depends on" }), _jsx("div", { className: "max-h-56 overflow-y-auto p-1", children: dependencyAddCandidates.length === 0 ? (_jsx("div", { className: "px-2 py-3 text-center text-xs text-gray-500", children: "All other tasks are already linked." })) : (dependencyAddCandidates.map((task) => (_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-xs hover:bg-gray-100", onClick: () => handleAddDependency(task.id), "data-testid": `project-template-dependency-option-${task.id}`, children: [_jsx(Link2, { className: "h-3.5 w-3.5 shrink-0 text-gray-400", strokeWidth: 1.5 }), _jsx("span", { className: "min-w-0 truncate font-medium text-gray-900", children: task.title || "Untitled Task" })] }, task.id)))) })] })] })] }), _jsx("p", { className: "mb-2 text-[11px] leading-snug text-gray-500", children: "This task must wait for these tasks to finish first." }), dependencyTasks.length === 0 ? (_jsx("div", { className: "text-xs text-gray-500", children: draftTemplate.taskTemplates.filter((t) => t.id !== selectedTask.id).length === 0
|
|
1516
|
+
? "Add more task templates to create dependencies."
|
|
1517
|
+
: "No dependencies yet. Use + to add one." })) : (_jsx("ul", { className: "space-y-1.5", children: dependencyTasks.map((task) => (_jsxs("li", { tabIndex: 0, className: cn("flex items-center justify-between gap-2 rounded-md border bg-white px-3 py-2 text-xs transition-colors outline-none", selectedDependencyId === task.id
|
|
1518
|
+
? "border-amber-300 bg-amber-50 ring-1 ring-amber-200"
|
|
1519
|
+
: "border-gray-200"), "aria-selected": selectedDependencyId === task.id, onClick: () => setSelectedDependencyId(task.id), onFocus: () => setSelectedDependencyId(task.id), "data-testid": `project-template-dependency-row-${task.id}`, children: [_jsx("span", { className: "min-w-0 truncate font-medium text-gray-800", children: task.title || "Untitled Task" }), _jsx("button", { type: "button", className: "shrink-0 rounded p-0.5 text-gray-400 hover:bg-gray-100 hover:text-gray-700", "aria-label": `Remove dependency on ${task.title}`, onClick: () => handleRemoveDependency(task.id), "data-testid": `project-template-remove-dependency-${task.id}`, children: _jsx(X, { className: "h-4 w-4", strokeWidth: 1.5 }) })] }, task.id))) }))] }), _jsxs("div", { className: "rounded-lg border border-gray-200 bg-gray-50 p-3", "data-testid": "project-template-child-tasks-section", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800", children: [_jsx(CornerDownRight, { className: "h-4 w-4 text-gray-500", strokeWidth: 1.5 }), "Child tasks"] }), _jsx(Button, { type: "button", size: "sm", variant: "outline", className: "h-7 gap-1 px-2", onClick: () => void handleOpenAddChildTaskDialog(), disabled: creatingChildTask, "aria-label": "Add child task", "data-testid": "project-template-add-child-task-button", children: _jsx(Plus, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) })] }), _jsx("p", { className: "mb-2 text-[11px] leading-snug text-gray-500", children: "Tasks that run after this one (they depend on this task)." }), childTaskTemplates.length === 0 ? (_jsx("div", { className: "text-xs text-gray-500", children: "No child tasks yet. Use + to add one." })) : (_jsx("ul", { className: "space-y-1.5", children: childTaskTemplates.map((task) => (_jsxs("li", { className: "flex items-center justify-between gap-2 rounded-md border border-gray-200 bg-white px-3 py-2 text-xs", "data-testid": `project-template-child-task-row-${task.id}`, children: [_jsx("span", { className: "min-w-0 truncate font-medium text-gray-800", children: task.title || "Untitled Task" }), _jsx("button", { type: "button", className: "shrink-0 rounded p-0.5 text-gray-400 hover:bg-gray-100 hover:text-gray-700", "aria-label": `Unlink child task ${task.title}`, onClick: () => handleUnlinkChildTask(task.id), "data-testid": `project-template-unlink-child-task-${task.id}`, children: _jsx(X, { className: "h-4 w-4", strokeWidth: 1.5 }) })] }, task.id))) }))] })] })] }) })] })) }));
|
|
1520
|
+
if (isMobile) {
|
|
1521
|
+
return (_jsxs("div", { className: "flex shrink-0 flex-col border-b border-gray-100", children: [_jsx("div", { className: "h-[300px] shrink-0 overflow-hidden border-b border-gray-100", children: graphView }), taskPane] }));
|
|
1522
|
+
}
|
|
1523
|
+
return (_jsx("div", { className: "min-h-[360px] flex-1 overflow-hidden border-b border-gray-100", children: _jsx(Splitter, { direction: "vertical", localStorageKey: "settings-project-template-graph-task-splitter-v1", className: "h-full", panels: [
|
|
1524
|
+
{
|
|
1525
|
+
name: "project-template-graph",
|
|
1526
|
+
defaultSize: 400,
|
|
1527
|
+
className: "min-h-0",
|
|
1528
|
+
content: graphView,
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
name: "project-template-task-details",
|
|
1532
|
+
defaultSize: "auto",
|
|
1533
|
+
className: "min-h-0",
|
|
1534
|
+
content: taskPane,
|
|
1535
|
+
},
|
|
1536
|
+
] }) }));
|
|
1537
|
+
};
|
|
1538
|
+
const detailContent = draftTemplate ? (_jsxs("div", { className: "flex h-full flex-col bg-gray-50/40", "data-testid": "project-template-detail-pane", children: [_jsxs("div", { className: cn("gap-3 border-b border-gray-200 bg-white px-5 py-3.5", isMobile
|
|
1539
|
+
? "grid grid-cols-[minmax(0,1fr)_auto] items-center gap-x-3"
|
|
1540
|
+
: "flex items-center justify-between"), children: [_jsx("div", { className: "min-w-0", children: _jsxs("div", { className: "flex items-center gap-2", children: [isMobile ? (_jsx("button", { type: "button", className: "shrink-0 rounded-md p-1.5 text-gray-700 hover:bg-gray-100 md:hidden", onClick: handleMobileBackToList, "aria-label": "Back to template list", "data-testid": "project-template-mobile-back-to-list", children: _jsx(ChevronLeft, { className: "h-5 w-5", strokeWidth: 1.5 }) })) : null, _jsx("div", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-gray-900 text-white", children: _jsx(GitBranch, { className: "h-4 w-4", strokeWidth: 1.5 }) }), _jsxs("div", { className: "min-w-0", children: [_jsx("h2", { className: "truncate text-xs font-semibold text-gray-900", children: draftTemplate.name || "Project Template" }), _jsxs("div", { className: "mt-0.5 flex min-h-[20px] items-center gap-2 text-[11px] text-gray-500", children: [_jsxs("span", { children: [draftTemplate.taskTemplates.length, " task templates"] }), headerStatusBadge] })] })] }) }), _jsxs("div", { className: "flex shrink-0 items-center gap-1.5 self-center bg-white pl-1", children: [isMobile ? (_jsx(Button, { type: "button", variant: "outline", size: "icon", className: "h-9 w-9 shrink-0 md:hidden", onClick: () => setMobileView("agent"), "aria-label": "Open template assistant", title: "Open template assistant", "data-testid": "project-template-mobile-open-assistant", children: _jsx(Bot, { className: "h-4 w-4", strokeWidth: 1.5 }) })) : null, !draftTemplate.isSystem &&
|
|
1541
|
+
(isMobile ? (_jsx(Button, { variant: "outline", size: "icon", className: "h-9 w-9 shrink-0", onClick: () => handleDeleteTemplate(), disabled: deleting, "aria-label": deleting ? "Deleting template" : "Delete template", title: deleting ? "Deleting…" : "Delete template", "data-testid": "project-template-delete-button", children: deleting ? (_jsx(RefreshCw, { className: "h-4 w-4 animate-spin", strokeWidth: 1.5 })) : (_jsx(Trash2, { className: "h-4 w-4", strokeWidth: 1.5 })) })) : (_jsxs(Button, { variant: "outline", size: "sm", onClick: () => handleDeleteTemplate(), disabled: deleting, "data-testid": "project-template-delete-button", children: [_jsx(Trash2, { className: "h-4 w-4", strokeWidth: 1.5 }), deleting ? "Deleting..." : "Delete"] })))] })] }), _jsxs("div", { className: "flex min-h-0 flex-1 flex-col gap-5 overflow-y-auto overscroll-y-contain p-5 md:overflow-hidden", children: [_jsxs("div", { className: "flex shrink-0 flex-col gap-5 md:max-h-[50vh] md:min-h-0 md:overflow-y-auto md:overscroll-y-contain", children: [saveError && (_jsx("div", { className: "rounded border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-700", children: saveError })), _jsxs("section", { className: "shrink-0 rounded-lg border border-gray-200 bg-white shadow-sm md:overflow-hidden", children: [_jsxs("button", { type: "button", className: "flex w-full items-center justify-between gap-3 px-4 py-3 text-left", onClick: () => setIsBasicSettingsExpanded((currentValue) => !currentValue), "aria-expanded": isBasicSettingsExpanded, "aria-controls": "project-template-basic-settings", "data-testid": "project-template-basic-settings-toggle", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-gray-100 text-gray-500", children: _jsx(Settings2, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-xs font-semibold tracking-wide text-gray-800", children: "Basic Settings" }), _jsx("p", { className: "text-[11px] leading-tight text-gray-400", children: "Configure the shared defaults for this project template." })] })] }), _jsx(ChevronDown, { className: cn("h-4 w-4 shrink-0 text-gray-400 transition-transform", isBasicSettingsExpanded ? "rotate-180" : "rotate-0"), strokeWidth: 1.5 })] }), isBasicSettingsExpanded && (_jsxs("div", { id: "project-template-basic-settings", className: "grid gap-4 border-t border-gray-100 p-4 md:grid-cols-2", children: [_jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsx(Label, { className: "text-xs", children: "Template name" }), _jsx(Input, { value: draftTemplate.name, onChange: (event) => applyDraftChange((currentTemplate) => ({
|
|
1542
|
+
...currentTemplate,
|
|
1543
|
+
name: event.target.value,
|
|
1544
|
+
})), placeholder: "Project template name", className: "text-xs md:text-xs", "data-testid": "project-template-name-input" })] }), _jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsx(Label, { className: "text-xs", children: "Description" }), _jsx(Textarea, { className: "text-xs", value: draftTemplate.description ?? "", onChange: (event) => applyDraftChange((currentTemplate) => ({
|
|
1545
|
+
...currentTemplate,
|
|
1546
|
+
description: event.target.value,
|
|
1547
|
+
})), rows: 4, placeholder: "Describe when to use this template", "data-testid": "project-template-description-input" })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { className: "text-xs", children: "Default cost limit" }), _jsx(Input, { type: "number", value: draftTemplate.defaultCostLimit ?? "", onChange: (event) => applyDraftChange((currentTemplate) => ({
|
|
1548
|
+
...currentTemplate,
|
|
1549
|
+
defaultCostLimit: event.target.value.trim() === ""
|
|
1550
|
+
? null
|
|
1551
|
+
: Number(event.target.value),
|
|
1552
|
+
})), placeholder: "0", className: "text-xs md:text-xs", "data-testid": "project-template-default-cost-limit-input" })] }), _jsxs("div", { className: "flex items-center justify-between gap-4 md:col-span-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs font-medium text-zinc-900", children: "Disabled" }), _jsx("div", { className: "mt-0.5 text-xs text-zinc-500", children: "Makes the template unavailable for project creation and template resolution." })] }), _jsx(Switch, { checked: draftTemplate.disabled === true, onCheckedChange: (checked) => applyDraftChange((currentTemplate) => ({
|
|
1553
|
+
...currentTemplate,
|
|
1554
|
+
disabled: checked,
|
|
1555
|
+
})), className: "data-[state=checked]:bg-amber-600 data-[state=checked]:shadow-inner dark:data-[state=checked]:bg-amber-600", "aria-label": "Disable this project template", "data-testid": "project-template-disabled-switch" })] }), _jsxs("div", { className: "flex items-center justify-between gap-4 md:col-span-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs font-medium text-zinc-900", children: "Hide from create dialog" }), _jsx("div", { className: "mt-0.5 text-xs text-zinc-500", children: "Keeps the template active, but removes it from the create project dialog." })] }), _jsx(Switch, { checked: draftTemplate.hideFromCreateDialog === true, onCheckedChange: (checked) => applyDraftChange((currentTemplate) => ({
|
|
1556
|
+
...currentTemplate,
|
|
1557
|
+
hideFromCreateDialog: checked,
|
|
1558
|
+
})), "aria-label": "Hide this project template from the create dialog", "data-testid": "project-template-hide-from-create-dialog-switch" })] }), _jsxs("div", { className: "flex items-center justify-between gap-4 md:col-span-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx("div", { className: "text-xs font-medium text-zinc-900", children: "Open in wizard mode" }), _jsx("div", { className: "mt-0.5 text-xs text-zinc-500", children: "New projects created from this template start in taskboard wizard mode." })] }), _jsx(Switch, { checked: draftTemplate.openInWizardMode === true, onCheckedChange: (checked) => applyDraftChange((currentTemplate) => ({
|
|
1447
1559
|
...currentTemplate,
|
|
1448
|
-
|
|
1449
|
-
}));
|
|
1450
|
-
|
|
1451
|
-
} }) })),
|
|
1452
|
-
},
|
|
1453
|
-
{
|
|
1454
|
-
name: "project-template-task-details",
|
|
1455
|
-
defaultSize: "auto",
|
|
1456
|
-
className: "min-h-0",
|
|
1457
|
-
content: (_jsx("div", { className: "flex h-full min-h-0 flex-col overflow-hidden", "data-testid": "project-template-task-detail-pane", children: !selectedTask ? (_jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-4", children: _jsx("div", { className: "rounded-lg border border-dashed border-gray-200 bg-gray-50 p-6 text-center text-xs text-gray-500", children: "Select a task node to edit its details." }) })) : (_jsxs(_Fragment, { children: [_jsx("div", { className: "shrink-0 border-b border-gray-100 bg-white px-4 pt-4 pb-3", children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { children: [_jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "text-xs font-semibold text-gray-900", "data-testid": "project-template-selected-task-title", children: selectedTask.title || "Untitled Task" }), selectedTask.disabled ? (_jsx(Badge, { variant: "outline", className: "text-[10px] uppercase", children: "Disabled" })) : null] }), _jsx("div", { className: "text-xs text-gray-500", children: "Configure task details, assignment, and dependencies." })] }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleDeleteSelectedTask, "data-testid": "project-template-remove-task-button", children: [_jsx(Trash2, { className: "h-4 w-4", strokeWidth: 1.5 }), "Remove Task"] })] }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-y-auto px-4 pt-3 pb-4", children: _jsxs("div", { className: "space-y-4", children: [_jsxs("div", { className: "grid gap-4 md:grid-cols-2", children: [_jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsx(Label, { className: "text-xs", children: "Task title" }), _jsx(Input, { value: selectedTask.title, onChange: (event) => updateSelectedTask((currentTask) => ({
|
|
1458
|
-
...currentTask,
|
|
1459
|
-
title: event.target.value,
|
|
1460
|
-
})), placeholder: "Task title", className: "text-xs md:text-xs", "data-testid": "project-template-task-title-input" })] }), _jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsx(Label, { className: "text-xs", children: "Description" }), _jsx(Textarea, { className: "text-xs", value: selectedTask.description ?? "", onChange: (event) => updateSelectedTask((currentTask) => ({
|
|
1461
|
-
...currentTask,
|
|
1462
|
-
description: event.target.value,
|
|
1463
|
-
})), rows: 4, placeholder: "Describe what this task should accomplish", "data-testid": "project-template-task-description-input" })] }), _jsxs("div", { className: "grid gap-1.5", children: [_jsx(Label, { className: "text-xs", children: "Priority" }), _jsx(Select, { className: "text-xs", value: selectedTask.priority ?? "", onValueChange: (value) => updateSelectedTask((currentTask) => ({
|
|
1464
|
-
...currentTask,
|
|
1465
|
-
priority: value || null,
|
|
1466
|
-
})), options: PRIORITY_OPTIONS, "data-testid": "project-template-task-priority-select" })] }), _jsxs("div", { className: "flex items-center justify-between gap-4 md:col-span-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsx(Label, { htmlFor: `project-template-task-disabled-${selectedTask.id}`, className: "text-xs font-medium text-zinc-900", children: "Disabled" }), _jsx("div", { className: "mt-0.5 text-xs text-zinc-500", children: "Keep this task in the template, but skip it when projects are created." })] }), _jsx(Switch, { id: `project-template-task-disabled-${selectedTask.id}`, checked: selectedTask.disabled === true, onCheckedChange: (checked) => updateSelectedTask((currentTask) => ({
|
|
1467
|
-
...currentTask,
|
|
1468
|
-
disabled: checked,
|
|
1469
|
-
})), "aria-label": "Disable this task template", "data-testid": "project-template-task-disabled-switch" })] }), _jsxs("div", { className: "grid gap-1.5 md:col-span-2", children: [_jsxs("div", { className: "flex items-center justify-between gap-2", children: [_jsx(Label, { className: "text-xs", children: "Default assignee" }), selectedTask.assigneeType === "Agent" ? (_jsxs(Button, { type: "button", size: "sm", variant: "outline", className: "h-7 gap-1 px-2 text-xs", onClick: () => void handleEditAssignedAgentProfile(), disabled: !selectedAgentProfileId, "data-testid": "project-template-task-edit-agent-profile-button", children: [_jsx(Settings2, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), "Edit Profile"] })) : null] }), _jsx(TaskAssigneePicker, { projectTemplateId: draftTemplate.id, assigneeType: selectedTask.assigneeType ?? null, value: selectedTask.assigneeId ?? null, displayValue: selectedTaskAssigneeDisplayValue, onChange: (next) => updateSelectedTask((currentTask) => ({
|
|
1470
|
-
...currentTask,
|
|
1471
|
-
assigneeType: next?.assigneeType ?? null,
|
|
1472
|
-
assigneeId: next?.assigneeId ?? null,
|
|
1473
|
-
agentProfileId: next?.assigneeType === "Agent"
|
|
1474
|
-
? next.assigneeId
|
|
1475
|
-
: null,
|
|
1476
|
-
})), placeholder: "Assign user or agent", buttonClassName: "h-8 w-full justify-between rounded-md bg-white text-xs font-medium", buttonTestId: "project-template-task-assignee-picker" }), profilesError ? (_jsx("div", { className: "text-xs text-amber-700", children: profilesError })) : null] })] }), _jsxs("div", { className: "space-y-3", children: [_jsxs("div", { className: "rounded-lg border border-gray-200 bg-gray-50 p-3", "data-testid": "project-template-dependencies-section", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800", children: [_jsx(Shield, { className: "h-4 w-4 text-gray-500", strokeWidth: 1.5 }), "Dependencies"] }), _jsxs(Popover, { open: dependencyPickerOpen, onOpenChange: setDependencyPickerOpen, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { type: "button", size: "sm", variant: "outline", className: "h-7 gap-1 px-2", disabled: dependencyAddCandidates.length === 0, "aria-label": "Add dependency", "data-testid": "project-template-add-dependency-button", children: _jsx(Plus, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) }) }), _jsxs(PopoverContent, { align: "end", className: "w-72 p-0", sideOffset: 6, "data-testid": "project-template-dependency-popover", children: [_jsx("div", { className: "border-b border-gray-100 px-3 py-2 text-xs font-medium text-gray-700", children: "Depends on" }), _jsx("div", { className: "max-h-56 overflow-y-auto p-1", children: dependencyAddCandidates.length ===
|
|
1477
|
-
0 ? (_jsx("div", { className: "px-2 py-3 text-center text-xs text-gray-500", children: "All other tasks are already linked." })) : (dependencyAddCandidates.map((task) => (_jsxs("button", { type: "button", className: "flex w-full items-center gap-2 rounded-md px-2 py-2 text-left text-xs hover:bg-gray-100", onClick: () => handleAddDependency(task.id), "data-testid": `project-template-dependency-option-${task.id}`, children: [_jsx(Link2, { className: "h-3.5 w-3.5 shrink-0 text-gray-400", strokeWidth: 1.5 }), _jsx("span", { className: "min-w-0 truncate font-medium text-gray-900", children: task.title ||
|
|
1478
|
-
"Untitled Task" })] }, task.id)))) })] })] })] }), _jsx("p", { className: "mb-2 text-[11px] leading-snug text-gray-500", children: "This task must wait for these tasks to finish first." }), dependencyTasks.length === 0 ? (_jsx("div", { className: "text-xs text-gray-500", children: draftTemplate.taskTemplates.filter((t) => t.id !== selectedTask.id).length === 0
|
|
1479
|
-
? "Add more task templates to create dependencies."
|
|
1480
|
-
: "No dependencies yet. Use + to add one." })) : (_jsx("ul", { className: "space-y-1.5", children: dependencyTasks.map((task) => (_jsxs("li", { tabIndex: 0, className: cn("flex items-center justify-between gap-2 rounded-md border bg-white px-3 py-2 text-xs outline-none transition-colors", selectedDependencyId === task.id
|
|
1481
|
-
? "border-amber-300 bg-amber-50 ring-1 ring-amber-200"
|
|
1482
|
-
: "border-gray-200"), "aria-selected": selectedDependencyId === task.id, onClick: () => setSelectedDependencyId(task.id), onFocus: () => setSelectedDependencyId(task.id), "data-testid": `project-template-dependency-row-${task.id}`, children: [_jsx("span", { className: "min-w-0 truncate font-medium text-gray-800", children: task.title || "Untitled Task" }), _jsx("button", { type: "button", className: "shrink-0 rounded p-0.5 text-gray-400 hover:bg-gray-100 hover:text-gray-700", "aria-label": `Remove dependency on ${task.title}`, onClick: () => handleRemoveDependency(task.id), "data-testid": `project-template-remove-dependency-${task.id}`, children: _jsx(X, { className: "h-4 w-4", strokeWidth: 1.5 }) })] }, task.id))) }))] }), _jsxs("div", { className: "rounded-lg border border-gray-200 bg-gray-50 p-3", "data-testid": "project-template-child-tasks-section", children: [_jsxs("div", { className: "mb-2 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-2 text-xs font-medium text-gray-800", children: [_jsx(CornerDownRight, { className: "h-4 w-4 text-gray-500", strokeWidth: 1.5 }), "Child tasks"] }), _jsx(Button, { type: "button", size: "sm", variant: "outline", className: "h-7 gap-1 px-2", onClick: () => void handleOpenAddChildTaskDialog(), disabled: creatingChildTask, "aria-label": "Add child task", "data-testid": "project-template-add-child-task-button", children: _jsx(Plus, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) })] }), _jsx("p", { className: "mb-2 text-[11px] leading-snug text-gray-500", children: "Tasks that run after this one (they depend on this task)." }), childTaskTemplates.length === 0 ? (_jsx("div", { className: "text-xs text-gray-500", children: "No child tasks yet. Use + to add one." })) : (_jsx("ul", { className: "space-y-1.5", children: childTaskTemplates.map((task) => (_jsxs("li", { className: "flex items-center justify-between gap-2 rounded-md border border-gray-200 bg-white px-3 py-2 text-xs", "data-testid": `project-template-child-task-row-${task.id}`, children: [_jsx("span", { className: "min-w-0 truncate font-medium text-gray-800", children: task.title || "Untitled Task" }), _jsx("button", { type: "button", className: "shrink-0 rounded p-0.5 text-gray-400 hover:bg-gray-100 hover:text-gray-700", "aria-label": `Unlink child task ${task.title}`, onClick: () => handleUnlinkChildTask(task.id), "data-testid": `project-template-unlink-child-task-${task.id}`, children: _jsx(X, { className: "h-4 w-4", strokeWidth: 1.5 }) })] }, task.id))) }))] })] })] }) })] })) })),
|
|
1483
|
-
},
|
|
1484
|
-
] }) })] })] })] })) : (_jsx("div", { className: "flex h-full items-center justify-center bg-gray-50/40", children: _jsxs("div", { className: "max-w-sm rounded-lg border border-dashed border-gray-300 bg-white p-8 text-center", children: [_jsx(GitBranch, { className: "mx-auto mb-3 h-10 w-10 text-gray-300", strokeWidth: 1.2 }), _jsx("h3", { className: "text-xs font-semibold text-gray-900", children: "Select a project template" }), _jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Choose a template from the list or create a new one to start editing." })] }) }));
|
|
1485
|
-
const agentPanelContent = (_jsx(ProjectTemplateAgentPanel, { templateName: draftTemplate?.name ?? null, agentId: templateAgentId, loading: templateAgentLoading, resetting: templateAgentResetting, error: templateAgentError, onStartOver: () => void handleResetTemplateAgent() }));
|
|
1560
|
+
openInWizardMode: checked,
|
|
1561
|
+
})), "aria-label": "Open new projects from this template in wizard mode", "data-testid": "project-template-open-in-wizard-mode-switch" })] })] }))] })] }), _jsxs("section", { className: "flex shrink-0 flex-col rounded-lg border border-gray-200 bg-white shadow-sm md:min-h-0 md:flex-1 md:overflow-hidden", children: [_jsxs("div", { className: "flex shrink-0 items-center justify-between gap-3 border-b border-gray-100 px-4 py-3", children: [_jsxs("div", { className: "flex items-center gap-2.5", children: [_jsx("span", { className: "flex h-6 w-6 items-center justify-center rounded-md bg-gray-100 text-gray-500", children: _jsx(GitBranch, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) }), _jsxs("div", { children: [_jsx("h3", { className: "text-xs font-semibold tracking-wide text-gray-800", children: "Template Task Editor" }), _jsx("p", { className: "text-[11px] leading-tight text-gray-400", children: "Add task templates, arrange them in the graph, and define dependencies." })] })] }), _jsxs(Button, { size: "sm", variant: "outline", onClick: () => void handleOpenCreateTaskDialog(), disabled: creatingTask, "data-testid": "project-template-add-task-button", children: [_jsx(Plus, { className: "h-4 w-4", strokeWidth: 1.5 }), "Add Task"] })] }), renderTemplateTaskEditorMain()] })] })] })) : (_jsx("div", { className: "flex h-full items-center justify-center bg-gray-50/40", children: _jsxs("div", { className: "max-w-sm rounded-lg border border-dashed border-gray-300 bg-white p-8 text-center", children: [_jsx(GitBranch, { className: "mx-auto mb-3 h-10 w-10 text-gray-300", strokeWidth: 1.2 }), _jsx("h3", { className: "text-xs font-semibold text-gray-900", children: "Select a project template" }), _jsx("p", { className: "mt-1 text-xs text-gray-500", children: "Choose a template from the list or create a new one to start editing." })] }) }));
|
|
1562
|
+
const agentPanelContent = (_jsx(ProjectTemplateAgentPanel, { templateName: draftTemplate?.name ?? null, agentId: templateAgentId, loading: templateAgentLoading, resetting: templateAgentResetting, error: templateAgentError, onStartOver: () => void handleResetTemplateAgent(), onExpandAssistant: () => setDesktopAssistantOnly(true), showExpandAssistant: !isMobile && !desktopAssistantOnly && !!draftTemplate, headerStart: isMobile ? (_jsx(Button, { type: "button", variant: "ghost", size: "sm", className: "gap-1 px-2", onClick: handleMobileBackFromAgent, "data-testid": "project-template-mobile-back-from-agent", children: _jsx(ChevronLeft, { className: "h-5 w-5", strokeWidth: 1.5 }) })) : undefined }));
|
|
1486
1563
|
const rightPanelContent = editingAgentProfile ? (_jsx(AgentProfileEditorPanel, { agent: editingAgentProfile, onClose: handleCloseEditingAgentProfile, actions: _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void handleDuplicateAssignedAgentProfile(), disabled: !selectedAgentProfileId ||
|
|
1487
1564
|
!editContext ||
|
|
1488
1565
|
duplicatingAgentProfile ||
|
|
1489
1566
|
editingAgentProfile.id !== selectedAgentProfileId, "data-testid": "project-template-task-duplicate-agent-profile-button", children: [_jsx(CopyPlus, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), duplicatingAgentProfile ? "Duplicating..." : "Duplicate"] }) })) : (agentPanelContent);
|
|
1567
|
+
const rightPanelForSplitter = !isMobile && desktopAssistantOnly ? (_jsxs("div", { className: "flex h-full min-h-0 flex-col bg-white", "data-testid": "project-template-desktop-assistant-only", children: [_jsx("div", { className: "flex shrink-0 items-center gap-2 border-b border-gray-200 bg-white px-2 py-2", children: _jsxs(Button, { type: "button", variant: "ghost", size: "sm", className: "gap-1 px-2", onClick: () => setDesktopAssistantOnly(false), "data-testid": "project-template-desktop-back-from-assistant-only", children: [_jsx(ChevronLeft, { className: "h-5 w-5", strokeWidth: 1.5 }), "Back to template editor"] }) }), _jsx("div", { className: "min-h-0 flex-1 overflow-hidden", children: rightPanelContent })] })) : (rightPanelContent);
|
|
1490
1568
|
const panels = [
|
|
1491
1569
|
{
|
|
1492
1570
|
name: "project-template-list",
|
|
1493
1571
|
defaultSize: 360,
|
|
1494
1572
|
content: listContent,
|
|
1495
1573
|
className: "overflow-hidden",
|
|
1574
|
+
hidden: desktopAssistantOnly || (isMobile && mobileView !== "list"),
|
|
1496
1575
|
},
|
|
1497
1576
|
{
|
|
1498
1577
|
name: "project-template-detail",
|
|
1499
1578
|
defaultSize: "auto",
|
|
1500
1579
|
content: detailContent,
|
|
1501
1580
|
className: "overflow-hidden",
|
|
1581
|
+
hidden: desktopAssistantOnly || (isMobile && mobileView !== "detail"),
|
|
1502
1582
|
},
|
|
1503
1583
|
{
|
|
1504
1584
|
name: "project-template-agent",
|
|
1505
1585
|
defaultSize: 460,
|
|
1506
|
-
content:
|
|
1586
|
+
content: rightPanelForSplitter,
|
|
1507
1587
|
className: "overflow-hidden",
|
|
1508
1588
|
collapsible: true,
|
|
1589
|
+
hidden: isMobile && mobileView !== "agent",
|
|
1509
1590
|
},
|
|
1510
1591
|
];
|
|
1511
1592
|
return (_jsxs(_Fragment, { children: [_jsx("div", { className: "h-full", "data-testid": "project-template-editor", onKeyDownCapture: handleProjectTemplateEditorKeyDownCapture, children: _jsx(Splitter, { panels: panels, localStorageKey: "settings-project-templates-panel-splitter-v2", direction: "horizontal", className: "h-full" }) }), _jsx(Dialog, { open: isCreateTaskDialogOpen, onOpenChange: (open) => {
|