From b04c11aae4239c69a7acc2bc0fa07708374739be Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 5 Jun 2026 00:36:51 -0700 Subject: [PATCH] feat(dashboard): move columns and fields panels into the editor sidebar --- .../app/components/WorkflowNodeEditor.css | 66 ++++++++++- .../app/components/WorkflowNodeEditor.tsx | 107 ++++++++++++++---- 2 files changed, 152 insertions(+), 21 deletions(-) diff --git a/packages/dashboard/app/components/WorkflowNodeEditor.css b/packages/dashboard/app/components/WorkflowNodeEditor.css index 2d100a4964..0a4c759d38 100644 --- a/packages/dashboard/app/components/WorkflowNodeEditor.css +++ b/packages/dashboard/app/components/WorkflowNodeEditor.css @@ -80,12 +80,76 @@ display: flex; flex-direction: column; gap: var(--space-xs); - width: 220px; + width: 300px; padding: var(--space-sm); border-right: 1px solid var(--border); overflow-y: auto; } +/* U12: columns + fields authoring sections moved into the left sidebar, below + the workflow list. Each is a collapsible disclosure whose toggle button is the + section header; the panels' own internal

is suppressed to avoid a double + header. */ +.wf-sidebar-panels { + display: flex; + flex-direction: column; + gap: var(--space-xs); + margin-top: var(--space-sm); + padding-top: var(--space-sm); + border-top: 1px solid var(--border); +} + +.wf-sidebar-section { + display: flex; + flex-direction: column; + gap: var(--space-xs); +} + +.wf-sidebar-section-toggle { + display: flex; + align-items: center; + gap: var(--space-xs); + width: 100%; + padding: var(--space-xs) var(--space-sm); + background: transparent; + border: none; + border-radius: var(--radius-sm); + color: var(--text); + font-size: 0.7rem; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.04em; + cursor: pointer; +} + +.wf-sidebar-section-toggle:hover { + background: var(--bg-secondary); +} + +/* When nested in the sidebar disclosure, the panels are stacked full-width + blocks rather than side columns: drop the dedicated width/border and let them + flow inside the sidebar's own scroll. */ +.wf-sidebar-section .wf-column-panel, +.wf-sidebar-section .wf-fields-panel { + width: auto; + min-width: 0; + padding: 0 var(--space-xs) var(--space-xs); + border-left: none; + overflow-y: visible; +} + +/* The disclosure toggle is the visible section header; hide the panels' own + title heading to avoid a duplicate. The Add button (also in the header) stays. */ +.wf-sidebar-section .wf-column-panel-header h3, +.wf-sidebar-section .wf-fields-panel-header h3 { + display: none; +} + +.wf-sidebar-section .wf-column-panel-header, +.wf-sidebar-section .wf-fields-panel-header { + justify-content: flex-end; +} + .wf-editor-new { display: inline-flex; align-items: center; diff --git a/packages/dashboard/app/components/WorkflowNodeEditor.tsx b/packages/dashboard/app/components/WorkflowNodeEditor.tsx index 5273a104d3..545a040fc0 100644 --- a/packages/dashboard/app/components/WorkflowNodeEditor.tsx +++ b/packages/dashboard/app/components/WorkflowNodeEditor.tsx @@ -668,6 +668,40 @@ function InnerEditor({ }); const [templateFilter, setTemplateFilter] = useState(""); const [templateConflict, setTemplateConflict] = useState(null); + + // U12: the columns/fields authoring panels live in the left sidebar (below the + // workflow list) as collapsible disclosure sections. Each section's collapsed + // state persists in localStorage; default expanded. + const columnsCollapsedStorageKey = "fusion:wf-sidebar-columns-collapsed"; + const fieldsCollapsedStorageKey = "fusion:wf-sidebar-fields-collapsed"; + const [columnsCollapsed, setColumnsCollapsed] = useState(() => { + try { + return localStorage.getItem(columnsCollapsedStorageKey) === "1"; + } catch { + return false; + } + }); + const [fieldsCollapsed, setFieldsCollapsed] = useState(() => { + try { + return localStorage.getItem(fieldsCollapsedStorageKey) === "1"; + } catch { + return false; + } + }); + useEffect(() => { + try { + localStorage.setItem(columnsCollapsedStorageKey, columnsCollapsed ? "1" : "0"); + } catch { + // localStorage unavailable (private mode / SSR): non-fatal. + } + }, [columnsCollapsed]); + useEffect(() => { + try { + localStorage.setItem(fieldsCollapsedStorageKey, fieldsCollapsed ? "1" : "0"); + } catch { + // localStorage unavailable (private mode / SSR): non-fatal. + } + }, [fieldsCollapsed]); // Wrapper around so keyboard deletion can return focus to the // canvas container (R6) instead of leaving it on a now-removed node. const canvasRef = useRef(null); @@ -1873,6 +1907,59 @@ function InnerEditor({ ))} )} + + {/* U12: columns + fields authoring panels, below the workflow list, + each as a collapsible disclosure section. Only shown when a + workflow is active (read-only gating preserved via isBuiltin). The + disclosure button serves as the section header; the panels' own + internal

is suppressed via CSS to avoid a double header. */} + {activeWorkflow && ( +
+
+ + {!columnsCollapsed && ( + + )} +
+ +
+ + {!fieldsCollapsed && ( + + )} +
+
+ )}
@@ -2336,26 +2423,6 @@ function InnerEditor({ )}
- {activeWorkflow && ( - - )} - - {activeWorkflow && ( - - )} - {selectedNode && selectedNode.data.kind !== "start" && selectedNode.data.kind !== "end" && (