@parhelia/core 0.1.12436 → 0.1.12447

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.
Files changed (43) hide show
  1. package/dist/config/types.d.ts +4 -0
  2. package/dist/config/types.js.map +1 -1
  3. package/dist/editor/ContentTree.js +19 -7
  4. package/dist/editor/ContentTree.js.map +1 -1
  5. package/dist/editor/Editor.js.map +1 -1
  6. package/dist/editor/ai/AgentTerminal.js +59 -1
  7. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  8. package/dist/editor/services/serviceHelper.js +5 -2
  9. package/dist/editor/services/serviceHelper.js.map +1 -1
  10. package/dist/editor/settings/panels/PersistentLogsPanel.js +25 -2
  11. package/dist/editor/settings/panels/PersistentLogsPanel.js.map +1 -1
  12. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.d.ts +7 -1
  13. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js +3 -3
  14. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js.map +1 -1
  15. package/dist/editor/settings/panels/ProjectTemplatesPanel.js +143 -84
  16. package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
  17. package/dist/revision.d.ts +2 -2
  18. package/dist/revision.js +2 -2
  19. package/dist/task-board/TaskBoardWorkspace.js +1 -1
  20. package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
  21. package/dist/task-board/assigneeDisplay.js +1 -3
  22. package/dist/task-board/assigneeDisplay.js.map +1 -1
  23. package/dist/task-board/components/CreateProjectDialog.js +2 -1
  24. package/dist/task-board/components/CreateProjectDialog.js.map +1 -1
  25. package/dist/task-board/components/TaskboardPersistentLogPanel.js +32 -5
  26. package/dist/task-board/components/TaskboardPersistentLogPanel.js.map +1 -1
  27. package/dist/task-board/persistentLogCopy.d.ts +7 -0
  28. package/dist/task-board/persistentLogCopy.js +80 -0
  29. package/dist/task-board/persistentLogCopy.js.map +1 -0
  30. package/dist/task-board/types.d.ts +3 -0
  31. package/dist/task-board/utils/taskDependencyOrdering.d.ts +6 -0
  32. package/dist/task-board/utils/taskDependencyOrdering.js +138 -1
  33. package/dist/task-board/utils/taskDependencyOrdering.js.map +1 -1
  34. package/dist/task-board/views/DependencyGraphView.d.ts +5 -2
  35. package/dist/task-board/views/DependencyGraphView.js +178 -61
  36. package/dist/task-board/views/DependencyGraphView.js.map +1 -1
  37. package/dist/task-board/views/WizardView.js +26 -24
  38. package/dist/task-board/views/WizardView.js.map +1 -1
  39. package/dist/tour/Tour.js +63 -0
  40. package/dist/tour/Tour.js.map +1 -1
  41. package/dist/tour/default-tour.js +7 -0
  42. package/dist/tour/default-tour.js.map +1 -1
  43. 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
- /** Query param for deep-linking the selected project template (settings reload). */
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 && PROJECT_TEMPLATE_ROOT_PATHS.some((root) => path.startsWith(root))) {
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();
@@ -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);
@@ -407,6 +423,7 @@ export function ProjectTemplatesPanel() {
407
423
  setSelectedTemplateId(template?.id ?? null);
408
424
  setDraftTemplate(clonedTemplate);
409
425
  setEditingAgentProfile(null);
426
+ setDesktopAssistantOnly(false);
410
427
  setSelectedTaskId(getSelectedTaskIdForTemplate(clonedTemplate, preferredTaskId));
411
428
  setIsDirty(false);
412
429
  setSaveError(null);
@@ -420,6 +437,27 @@ export function ProjectTemplatesPanel() {
420
437
  });
421
438
  }
422
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]);
423
461
  const loadTemplates = useCallback(async (preferredTemplateId) => {
424
462
  try {
425
463
  setState("loading");
@@ -551,7 +589,7 @@ export function ProjectTemplatesPanel() {
551
589
  if (isDirty || saving) {
552
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..." }));
553
591
  }
554
- 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..." }));
555
593
  }, [isDirty, saveError, saving]);
556
594
  const graphTasks = useMemo(() => buildTemplateTaskItems(draftTemplate, aiProfilesById), [aiProfilesById, draftTemplate]);
557
595
  const graphDependencies = useMemo(() => buildTemplateDependencies(draftTemplate), [draftTemplate]);
@@ -581,7 +619,10 @@ export function ProjectTemplatesPanel() {
581
619
  }, [selectedAgentProfile, selectedAgentProfileId, selectedTask?.title]);
582
620
  const handleCloseEditingAgentProfile = useCallback(() => {
583
621
  setEditingAgentProfile(null);
584
- }, []);
622
+ if (isMobile) {
623
+ setMobileView("detail");
624
+ }
625
+ }, [isMobile]);
585
626
  const syncProjectTemplateAgentContext = useCallback(async (agentId, template, selectedTaskTemplateId) => {
586
627
  try {
587
628
  await updateAgentContext(agentId, buildProjectTemplateAgentContext(template, selectedTaskTemplateId));
@@ -999,8 +1040,11 @@ export function ProjectTemplatesPanel() {
999
1040
  editContext?.updateUrl({
1000
1041
  [SELECTED_PROJECT_TEMPLATE_QUERY_PARAM]: nextTemplate.id,
1001
1042
  });
1043
+ if (isMobile) {
1044
+ setMobileView("detail");
1045
+ }
1002
1046
  await persistTemplate(request, signature, { showErrorToast: true });
1003
- }, [editContext, flushPendingSave, persistTemplate, templates]);
1047
+ }, [editContext, flushPendingSave, isMobile, persistTemplate, templates]);
1004
1048
  const handleDeleteTemplate = useCallback((templateToDelete) => {
1005
1049
  const template = templateToDelete ?? draftTemplate;
1006
1050
  if (!template || template.isSystem)
@@ -1031,8 +1075,7 @@ export function ProjectTemplatesPanel() {
1031
1075
  }
1032
1076
  toast.success("Project template deleted");
1033
1077
  const fallbackId = isDeletingSelectedTemplate
1034
- ? templates.find((currentTemplate) => currentTemplate.id !== templateId)
1035
- ?.id ?? null
1078
+ ? (templates.find((currentTemplate) => currentTemplate.id !== templateId)?.id ?? null)
1036
1079
  : selectedTemplateIdRef.current;
1037
1080
  await loadTemplates(fallbackId);
1038
1081
  }
@@ -1063,7 +1106,10 @@ export function ProjectTemplatesPanel() {
1063
1106
  return;
1064
1107
  }
1065
1108
  selectTemplate(template);
1066
- }, [flushPendingSave, selectTemplate]);
1109
+ if (isMobile) {
1110
+ setMobileView("detail");
1111
+ }
1112
+ }, [flushPendingSave, selectTemplate, isMobile]);
1067
1113
  const handleRefreshTemplates = useCallback(async () => {
1068
1114
  const canContinue = await flushPendingSave({ showErrorToast: true });
1069
1115
  if (!canContinue) {
@@ -1431,106 +1477,119 @@ export function ProjectTemplatesPanel() {
1431
1477
  handleDeleteTemplate(template);
1432
1478
  }, children: _jsx(Trash2, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }) }))] }, template.id));
1433
1479
  }) })) })] }) }));
1434
- 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: "flex items-center justify-between gap-3 border-b border-gray-200 bg-white px-5 py-3.5", children: [_jsx("div", { className: "min-w-0", children: _jsxs("div", { className: "flex items-center gap-2", children: [_jsx("div", { className: "flex h-8 w-8 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] })] })] }) }), _jsx("div", { className: "flex shrink-0 items-center gap-2", children: !draftTemplate.isSystem && (_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-hidden p-5", 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 overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm", 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) => ({
1435
- ...currentTemplate,
1436
- name: event.target.value,
1437
- })), 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) => ({
1438
- ...currentTemplate,
1439
- description: event.target.value,
1440
- })), 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) => ({
1441
- ...currentTemplate,
1442
- defaultCostLimit: event.target.value.trim() === ""
1443
- ? null
1444
- : Number(event.target.value),
1445
- })), 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) => ({
1446
- ...currentTemplate,
1447
- disabled: checked,
1448
- })), 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) => ({
1449
- ...currentTemplate,
1450
- hideFromCreateDialog: checked,
1451
- })), "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) => ({
1452
- ...currentTemplate,
1453
- openInWizardMode: checked,
1454
- })), "aria-label": "Open new projects from this template in wizard mode", "data-testid": "project-template-open-in-wizard-mode-switch" })] })] }))] }), _jsxs("section", { className: "flex min-h-0 flex-1 flex-col overflow-hidden rounded-lg border border-gray-200 bg-white shadow-sm", 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"] })] }), _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: [
1455
- {
1456
- name: "project-template-graph",
1457
- defaultSize: 400,
1458
- className: "min-h-0",
1459
- content: (_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, orientation: "horizontal", autoLayoutStrategy: "hierarchy", savedLayout: draftTemplate.graphLayout, selectedTaskId: selectedTaskId, selectedDependencyId: selectedDependencyId, onSelectTask: handleSelectTask, onSelectDependency: handleSelectDependencyFromGraph, onClearDependencySelection: handleClearDependencySelection, onAddDependentTaskFromNode: (taskId) => {
1460
- void handleOpenCreateDependentTaskDialogForTask(taskId);
1461
- }, onAddChildTaskFromNode: (taskId) => {
1462
- void handleOpenAddChildTaskDialogForTask(taskId);
1463
- }, 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) => {
1464
- const nextLayout = {
1465
- ...layout,
1466
- projectTemplateId: draftTemplate.id,
1467
- };
1468
- applyDraftChange((currentTemplate) => ({
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) => ({
1469
1559
  ...currentTemplate,
1470
- graphLayout: nextLayout,
1471
- }));
1472
- return nextLayout;
1473
- } }) })),
1474
- },
1475
- {
1476
- name: "project-template-task-details",
1477
- defaultSize: "auto",
1478
- className: "min-h-0",
1479
- 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) => ({
1480
- ...currentTask,
1481
- title: event.target.value,
1482
- })), 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) => ({
1483
- ...currentTask,
1484
- description: event.target.value,
1485
- })), 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) => ({
1486
- ...currentTask,
1487
- priority: value || null,
1488
- })), 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) => ({
1489
- ...currentTask,
1490
- disabled: checked,
1491
- })), "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) => ({
1492
- ...currentTask,
1493
- assigneeType: next?.assigneeType ?? null,
1494
- assigneeId: next?.assigneeId ?? null,
1495
- agentProfileId: next?.assigneeType === "Agent"
1496
- ? next.assigneeId
1497
- : null,
1498
- })), 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 ===
1499
- 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 ||
1500
- "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
1501
- ? "Add more task templates to create dependencies."
1502
- : "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
1503
- ? "border-amber-300 bg-amber-50 ring-1 ring-amber-200"
1504
- : "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))) }))] })] })] }) })] })) })),
1505
- },
1506
- ] }) })] })] })] })) : (_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." })] }) }));
1507
- 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 }));
1508
1563
  const rightPanelContent = editingAgentProfile ? (_jsx(AgentProfileEditorPanel, { agent: editingAgentProfile, onClose: handleCloseEditingAgentProfile, actions: _jsxs(Button, { type: "button", variant: "outline", size: "sm", onClick: () => void handleDuplicateAssignedAgentProfile(), disabled: !selectedAgentProfileId ||
1509
1564
  !editContext ||
1510
1565
  duplicatingAgentProfile ||
1511
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);
1512
1568
  const panels = [
1513
1569
  {
1514
1570
  name: "project-template-list",
1515
1571
  defaultSize: 360,
1516
1572
  content: listContent,
1517
1573
  className: "overflow-hidden",
1574
+ hidden: desktopAssistantOnly || (isMobile && mobileView !== "list"),
1518
1575
  },
1519
1576
  {
1520
1577
  name: "project-template-detail",
1521
1578
  defaultSize: "auto",
1522
1579
  content: detailContent,
1523
1580
  className: "overflow-hidden",
1581
+ hidden: desktopAssistantOnly || (isMobile && mobileView !== "detail"),
1524
1582
  },
1525
1583
  {
1526
1584
  name: "project-template-agent",
1527
1585
  defaultSize: 460,
1528
- content: rightPanelContent,
1586
+ content: rightPanelForSplitter,
1529
1587
  className: "overflow-hidden",
1530
1588
  collapsible: true,
1589
+ hidden: isMobile && mobileView !== "agent",
1531
1590
  },
1532
1591
  ];
1533
- 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", forceExpandLastPanelKey: editingAgentProfile?.id ?? null }) }), _jsx(Dialog, { open: isCreateTaskDialogOpen, onOpenChange: (open) => {
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) => {
1534
1593
  setIsCreateTaskDialogOpen(open);
1535
1594
  if (!open) {
1536
1595
  setNewTaskDependencySourceId(null);