From a32c1240e5b432b6413ffc93e35b2ea316fdb622 Mon Sep 17 00:00:00 2001 From: abccodes Date: Tue, 16 Jun 2026 23:49:34 -0700 Subject: [PATCH] [aidan] ux/workflows: open New workflow in agent build chat instead of empty card When creating a new workflow from the hub, open it in edit_agent view (with the agent builder chat) instead of a preview card. The workflow is created on the backend first so the embedded session has a real ID. --- .../editor/ElementSelectionContext.tsx | 2 +- .../editor/useDomElementSelector.ts | 4 ++- .../Dashboard/canvas/DashboardCanvas.tsx | 2 ++ .../Dashboard/canvas/DashboardCardLayer.tsx | 8 ++++++ .../Dashboard/canvas/DashboardOverlays.tsx | 8 ++++++ .../app/pages/Dashboard/controls/Minimap.tsx | 26 +++++++++++++++---- .../pages/Dashboard/geometry/getCardRect.ts | 4 +++ .../hooks/state/useDashboardController.ts | 1 + .../hooks/state/useDashboardSelection.ts | 20 +++++++++++--- 9 files changed, 65 insertions(+), 10 deletions(-) diff --git a/frontend/src/app/components/editor/ElementSelectionContext.tsx b/frontend/src/app/components/editor/ElementSelectionContext.tsx index d3a77373..ece5a99c 100644 --- a/frontend/src/app/components/editor/ElementSelectionContext.tsx +++ b/frontend/src/app/components/editor/ElementSelectionContext.tsx @@ -10,7 +10,7 @@ export interface SelectedElement { computedStyles: Record; screenshot?: string; boundingRect: { x: number; y: number; width: number; height: number }; - semanticType?: 'agent-card' | 'message' | 'tool-call' | 'tool-group' | 'view-card' | 'browser-card' | 'dom-element'; + semanticType?: 'agent-card' | 'message' | 'tool-call' | 'tool-group' | 'view-card' | 'browser-card' | 'workflow-card' | 'workflows-hub-card' | 'dom-element'; semanticLabel?: string; semanticData?: Record; } diff --git a/frontend/src/app/components/editor/useDomElementSelector.ts b/frontend/src/app/components/editor/useDomElementSelector.ts index bf2df2fe..d0ea73ac 100644 --- a/frontend/src/app/components/editor/useDomElementSelector.ts +++ b/frontend/src/app/components/editor/useDomElementSelector.ts @@ -6,7 +6,7 @@ const SELECT_ATTR = 'data-select-type'; const SELECT_ID_ATTR = 'data-select-id'; const SELECT_META_ATTR = 'data-select-meta'; -const DRAG_SELECT_TYPES = ['agent-card', 'view-card', 'browser-card'] as const; +const DRAG_SELECT_TYPES = ['agent-card', 'view-card', 'browser-card', 'workflow-card', 'workflows-hub-card'] as const; const DRAG_SELECTOR = DRAG_SELECT_TYPES.map((t) => `[${SELECT_ATTR}="${t}"]`).join(','); export interface OverlayState { @@ -38,6 +38,8 @@ const SEMANTIC_LABELS: Record = { 'tool-group': 'Tool Group', 'view-card': 'View', 'browser-card': 'Browser', + 'workflow-card': 'Workflow', + 'workflows-hub-card': 'Workflows', }; function findSelectableAncestor(target: Element, excludeId?: string | null): Element | null { diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx index fa965cd5..a449a853 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCanvas.tsx @@ -289,6 +289,8 @@ const DashboardCanvas: React.FC = ({ cards={cards} viewCards={viewCards} browserCards={browserCards} + workflowCards={workflowCards} + workflowsHub={workflowsHub} focusedCardId={focusedCardId} shakeDirection={shakeDirection} neighborDirections={neighborDirections} diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx index 75af0b0d..2ab00dfa 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardCardLayer.tsx @@ -273,6 +273,14 @@ const DashboardCardLayer: React.FC = ({ zoom={zoom} panX={panX} panY={panY} + isSelected={selection.isSelected('workflows-hub')} + isHighlighted={highlightedCardId === 'workflows-hub'} + multiDragDelta={selection.isSelected('workflows-hub') ? multiDragDelta : null} + onCardSelect={onCardSelect} + onDragStart={onDragStart} + onDragMove={onDragMove} + onDragEnd={onDragEnd} + onBringToFront={onBringToFront} /> )} {Object.values(workflowCards).map((wc) => ( diff --git a/frontend/src/app/pages/Dashboard/canvas/DashboardOverlays.tsx b/frontend/src/app/pages/Dashboard/canvas/DashboardOverlays.tsx index 8d786970..2a54af0b 100644 --- a/frontend/src/app/pages/Dashboard/canvas/DashboardOverlays.tsx +++ b/frontend/src/app/pages/Dashboard/canvas/DashboardOverlays.tsx @@ -9,6 +9,8 @@ import type { CardPosition, ViewCardPosition, BrowserCardPosition, + WorkflowCardPosition, + WorkflowsHubPosition, } from '@/shared/state/dashboardLayoutSlice'; import type { useCanvasControls } from '../hooks/interaction/useCanvasControls'; @@ -23,6 +25,8 @@ interface DashboardOverlaysProps { cards: Record; viewCards: Record; browserCards: Record; + workflowCards: Record; + workflowsHub: WorkflowsHubPosition | null; focusedCardId: string | null; shakeDirection: Direction | null; neighborDirections: NeighborDirections; @@ -52,6 +56,8 @@ const DashboardOverlays: React.FC = ({ cards, viewCards, browserCards, + workflowCards, + workflowsHub, focusedCardId, shakeDirection, neighborDirections, @@ -121,6 +127,8 @@ const DashboardOverlays: React.FC = ({ cards, viewCards, browserCards, + workflowCards, + workflowsHub, }} onMinimapPan={(px, py) => canvas.actions.setState({ panX: px, panY: py, zoom: canvas.zoom })} /> diff --git a/frontend/src/app/pages/Dashboard/controls/Minimap.tsx b/frontend/src/app/pages/Dashboard/controls/Minimap.tsx index 7a2b7ebb..4bdee3b1 100644 --- a/frontend/src/app/pages/Dashboard/controls/Minimap.tsx +++ b/frontend/src/app/pages/Dashboard/controls/Minimap.tsx @@ -1,6 +1,6 @@ import React, { useRef, useCallback, useMemo } from 'react'; import { useClaudeTokens } from '@/shared/styles/ThemeContext'; -import type { CardPosition, ViewCardPosition, BrowserCardPosition } from '@/shared/state/dashboardLayoutSlice'; +import type { CardPosition, ViewCardPosition, BrowserCardPosition, WorkflowCardPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice'; const MINIMAP_W = 200; const MINIMAP_H = 140; @@ -14,6 +14,8 @@ export interface MinimapProps { cards: Record; viewCards: Record; browserCards: Record; + workflowCards: Record; + workflowsHub: WorkflowsHubPosition | null; onPan: (panX: number, panY: number) => void; } @@ -22,12 +24,12 @@ interface CardRect { y: number; width: number; height: number; - type: 'agent' | 'view' | 'browser'; + type: 'agent' | 'view' | 'browser' | 'workflow' | 'workflows-hub'; } const Minimap: React.FC = ({ panX, panY, zoom, viewportRef, - cards, viewCards, browserCards, + cards, viewCards, browserCards, workflowCards, workflowsHub, onPan, }) => { const c = useClaudeTokens(); @@ -45,8 +47,20 @@ const Minimap: React.FC = ({ for (const bc of Object.values(browserCards)) { result.push({ x: bc.x, y: bc.y, width: bc.width, height: bc.height, type: 'browser' }); } + for (const wc of Object.values(workflowCards)) { + result.push({ x: wc.x, y: wc.y, width: wc.width, height: wc.height, type: 'workflow' }); + } + if (workflowsHub) { + result.push({ + x: workflowsHub.x, + y: workflowsHub.y, + width: workflowsHub.width, + height: workflowsHub.height, + type: 'workflows-hub', + }); + } return result; - }, [cards, viewCards, browserCards]); + }, [cards, viewCards, browserCards, workflowCards, workflowsHub]); const layout = useMemo(() => { const vp = viewportRef.current; @@ -128,11 +142,13 @@ const Minimap: React.FC = ({ window.addEventListener('mouseup', onUp); }, [minimapToCanvas]); - const typeColor = (type: 'agent' | 'view' | 'browser') => { + const typeColor = (type: CardRect['type']) => { switch (type) { case 'agent': return c.accent.primary; case 'view': return c.status.info; case 'browser': return c.status.success; + case 'workflow': return c.status.warning; + case 'workflows-hub': return c.status.warning; } }; diff --git a/frontend/src/app/pages/Dashboard/geometry/getCardRect.ts b/frontend/src/app/pages/Dashboard/geometry/getCardRect.ts index 1d80881f..bcb03883 100644 --- a/frontend/src/app/pages/Dashboard/geometry/getCardRect.ts +++ b/frontend/src/app/pages/Dashboard/geometry/getCardRect.ts @@ -27,6 +27,10 @@ export function getCardRect(id: string, type: CardType): const wc = layoutState.workflowCards[id]; if (!wc) return undefined; return { x: wc.x, y: wc.y, width: wc.width, height: wc.height }; + } else if (type === 'workflows-hub') { + const hub = layoutState.workflowsHub; + if (!hub) return undefined; + return { x: hub.x, y: hub.y, width: hub.width, height: hub.height }; } return undefined; } diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts index 9f054123..2481bf11 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardController.ts @@ -55,6 +55,7 @@ export function useDashboardController(dashboardId: string, isActive: boolean) { browserCards, notes, workflowCards, + workflowsHub, ); const { toolbarRef, toolbarOpen, setToolbarOpen, searchPaletteOpen, setSearchPaletteOpen, diff --git a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts index b5ad9d64..efa0ae93 100644 --- a/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts +++ b/frontend/src/app/pages/Dashboard/hooks/state/useDashboardSelection.ts @@ -1,5 +1,5 @@ import { useState, useCallback, useRef, useEffect, RefObject } from 'react'; -import type { CardPosition, ViewCardPosition, BrowserCardPosition, NotePosition, WorkflowCardPosition } from '@/shared/state/dashboardLayoutSlice'; +import type { CardPosition, ViewCardPosition, BrowserCardPosition, NotePosition, WorkflowCardPosition, WorkflowsHubPosition } from '@/shared/state/dashboardLayoutSlice'; export type { CardType } from '@/shared/state/dashboardLayoutSlice'; import type { CardType } from '@/shared/state/dashboardLayoutSlice'; @@ -44,6 +44,7 @@ export function useDashboardSelection( browserCards: Record = {}, notes: Record = {}, workflowCards: Record = {}, + workflowsHub: WorkflowsHubPosition | null = null, ) { const [selectedIds, setSelectedIds] = useState>(new Map()); const [marquee, setMarquee] = useState(null); @@ -79,8 +80,9 @@ export function useDashboardSelection( for (const bc of Object.values(browserCards)) next.set(bc.browser_id, 'browser'); for (const n of Object.values(notes)) next.set(n.note_id, 'note'); for (const wc of Object.values(workflowCards)) next.set(wc.workflow_id, 'workflow'); + if (workflowsHub) next.set('workflows-hub', 'workflows-hub'); setSelectedIds(next); - }, [cards, viewCards, browserCards, notes, workflowCards]); + }, [cards, viewCards, browserCards, notes, workflowCards, workflowsHub]); const selectCard = useCallback( (id: string, type: CardType, shiftKey: boolean) => { @@ -176,6 +178,18 @@ export function useDashboardSelection( } } + if ( + workflowsHub && + rectsIntersect(rect, { + x: workflowsHub.x, + y: workflowsHub.y, + width: workflowsHub.width, + height: workflowsHub.height, + }) + ) { + intersecting.set('workflows-hub', 'workflows-hub'); + } + if (shiftKey) { const base = selectionBeforeMarqueeRef.current; const next = new Map(base); @@ -191,7 +205,7 @@ export function useDashboardSelection( return intersecting; }, - [cards, viewCards, browserCards, notes, workflowCards], + [cards, viewCards, browserCards, notes, workflowCards, workflowsHub], ); const handleCanvasMouseDown = useCallback(