diff --git a/packages/dashboard/app/components/AppModals.tsx b/packages/dashboard/app/components/AppModals.tsx index 61805592a1..ca2370ccf0 100644 --- a/packages/dashboard/app/components/AppModals.tsx +++ b/packages/dashboard/app/components/AppModals.tsx @@ -387,7 +387,6 @@ export function AppModals({ projectId={projectId} initialPanel={modalManager.workflowEditorInitialPanel} initialAction={modalManager.workflowEditorInitialAction} - initialWorkflowId={modalManager.workflowEditorInitialWorkflowId} /> diff --git a/packages/dashboard/app/components/WorkflowNodeEditor.css b/packages/dashboard/app/components/WorkflowNodeEditor.css index 80663155cd..cc91760be7 100644 --- a/packages/dashboard/app/components/WorkflowNodeEditor.css +++ b/packages/dashboard/app/components/WorkflowNodeEditor.css @@ -557,6 +557,38 @@ color: var(--text); } +.wf-inspector-heading { + display: flex; + align-items: center; + justify-content: space-between; + gap: var(--space-sm); +} + +.wf-inspector-toggle { + display: inline-flex; + align-items: center; + gap: var(--space-xs); + padding: var(--space-xs) var(--space-sm); + border: 1px solid var(--border); + border-radius: var(--radius-sm); + background: var(--bg-secondary); + color: var(--text); + font: inherit; + cursor: pointer; +} + +.wf-inspector-toggle:hover { + background: var(--bg-tertiary); +} + +.wf-inspector-toggle--collapsed { + position: absolute; + right: var(--space-sm); + bottom: var(--space-sm); + z-index: 5; + box-shadow: var(--shadow-sm); +} + .wf-field { display: flex; flex-direction: column; @@ -1225,6 +1257,11 @@ min-height: 0; } + .wf-editor-body--editor-stage .wf-editor-canvas { + flex: 1 1 auto; + min-height: 0; + } + .wf-editor-body--editor-stage .wf-editor-inspector { width: 100%; min-width: 0; @@ -1318,9 +1355,33 @@ } .wf-editor-toolbar { + flex-wrap: nowrap; overflow-x: auto; } + .wf-editor-palette, + .wf-editor-actions { + flex: 0 0 auto; + flex-wrap: nowrap; + } + + .wf-templates { + flex: 0 0 auto; + } + + .wf-templates-header { + flex-wrap: nowrap; + } + + .wf-templates-toggle { + flex: 0 0 auto; + } + + .wf-inspector-toggle { + min-height: var(--wf-editor-touch-target); + padding: var(--space-sm) var(--space-md); + } + .wf-ai-panel { position: fixed; inset: var(--space-sm); diff --git a/packages/dashboard/app/components/WorkflowNodeEditor.tsx b/packages/dashboard/app/components/WorkflowNodeEditor.tsx index d22484aa4c..e7339baf6e 100644 --- a/packages/dashboard/app/components/WorkflowNodeEditor.tsx +++ b/packages/dashboard/app/components/WorkflowNodeEditor.tsx @@ -173,8 +173,6 @@ interface WorkflowNodeEditorProps { initialPanel?: "settings"; /** When "create" the editor opens with the new-workflow dialog active. */ initialAction?: "create"; - /** Workflow id to pre-select when opening directly into an existing workflow. */ - initialWorkflowId?: string; } let nodeSeq = 0; @@ -659,7 +657,6 @@ function InnerEditor({ projectId, initialPanel, initialAction, - initialWorkflowId, modalRef, }: Omit & { modalRef: React.RefObject }) { const [workflows, setWorkflows] = useState([]); @@ -681,6 +678,7 @@ function InnerEditor({ const [edges, setEdges, onEdgesChange] = useEdgesState([]); const [selectedNodeId, setSelectedNodeId] = useState(null); const [selectedEdgeId, setSelectedEdgeId] = useState(null); + const [inspectorCollapsed, setInspectorCollapsed] = useState(false); const { t } = useTranslation("app"); const { confirm } = useConfirm(); // Create-workflow dialog (KTD-7) open state + focus-return ref to the @@ -728,7 +726,11 @@ function InnerEditor({ const templatesCollapsedStorageKey = "fusion:wf-templates-collapsed"; const [templatesCollapsed, setTemplatesCollapsed] = useState(() => { try { - return localStorage.getItem(templatesCollapsedStorageKey) === "1"; + const stored = localStorage.getItem(templatesCollapsedStorageKey); + if (stored != null) return stored === "1"; + return typeof window !== "undefined" && + typeof window.matchMedia === "function" && + window.matchMedia(MOBILE_MEDIA_QUERY).matches; } catch { return false; } @@ -915,6 +917,10 @@ function InnerEditor({ } }, [templatesCollapsed]); + useEffect(() => { + if (selectedNodeId) setInspectorCollapsed(false); + }, [selectedNodeId]); + // U9/R8: fragment definitions surface from the loaded workflow list (kind === // "fragment"); they are excluded from the sidebar workflow list elsewhere. const fragments = useMemo( @@ -975,22 +981,16 @@ function InnerEditor({ try { const data = await fetchWorkflows(projectId); setWorkflows(data); - const requestedWorkflow = initialWorkflowId - ? data.find((workflow) => workflow.id === initialWorkflowId) - : undefined; setActiveId((prev) => { if (prev && data.some((workflow) => workflow.id === prev)) return prev; - return requestedWorkflow?.id ?? (isMobileViewport ? null : data[0]?.id ?? null); + return isMobileViewport ? null : data[0]?.id ?? null; }); - if (requestedWorkflow && isMobileViewport) { - setWorkflowListStageOpen(false); - } } catch (err) { addToast(getErrorMessage(err) || "Failed to load workflows", "error"); } finally { setLoading(false); } - }, [projectId, addToast, isMobileViewport, initialWorkflowId]); + }, [projectId, addToast, isMobileViewport]); useEffect(() => { void loadWorkflows(); @@ -2677,6 +2677,22 @@ function InnerEditor({ )}
+ {isMobileViewport && + inspectorCollapsed && + selectedNode && + selectedNode.data.kind !== "start" && + selectedNode.data.kind !== "end" && ( + + )} {isTrivialUserGraph && (
{t( @@ -2743,9 +2759,26 @@ function InnerEditor({ )} - {selectedNode && selectedNode.data.kind !== "start" && selectedNode.data.kind !== "end" && ( -