Skip to content

Commit 1950049

Browse files
andresdjassoclaude
andcommitted
feat(workflow): stage stack flips both ways — slimmer, page-bg identity bars
The stack is no longer torn down when the workflow comes forward: flipping tucks the resource card into a small bottom-edge tab (active tab icon + name + a +N count) so both sides stay one click apart, with ?resource= persisting across flips. The workflow's back bar drops the grey fill for the page background and shrinks to a 30px visible band with a 12px icon and 12px title. Escape now flips instead of closing; only the card's × (or closing the last tab) dismantles the stack. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
1 parent d37d02a commit 1950049

1 file changed

Lines changed: 49 additions & 14 deletions

File tree

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/docked-chat/workflow-with-chat.tsx

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
MothershipResourcesProvider,
1010
MothershipView,
1111
} from '@/app/workspace/[workspaceId]/home/components'
12+
import { getResourceConfig } from '@/app/workspace/[workspaceId]/home/components/mothership-view/components/resource-registry'
1213
import type {
1314
MothershipResource,
1415
MothershipResourceType,
@@ -91,10 +92,14 @@ export function WorkflowWithChat() {
9192

9293
// ── Stage stack ──────────────────────────────────────────────────────────
9394
// Non-workflow resources never replace the editor: they slide in as a card
94-
// IN FRONT of it (toast-stack depth), with the workflow's title strip
95-
// peeking above. Clicking the peek, the ×, or pressing Escape brings the
96-
// workflow forward. Tabs are the same workspace-owned strip as everywhere.
95+
// IN FRONT of it (toast-stack depth) with the workflow's identity bar
96+
// peeking above. The stack is a two-way flip — bringing the workflow
97+
// forward tucks the resource card into a small tab at the bottom edge, so
98+
// both stay one click apart. Only the card's × (or closing the last tab)
99+
// tears the stack down. Tabs are the same workspace-owned strip as
100+
// everywhere.
97101
const [stackOpen, setStackOpen] = useState<boolean>(() => Boolean(searchParams.get('resource')))
102+
const [stageFront, setStageFront] = useState<'card' | 'editor'>('card')
98103
const initialStageIdRef = useRef(searchParams.get('resource'))
99104

100105
const workspaceTabs = useMothershipTabsStore((s) =>
@@ -109,19 +114,22 @@ export function WorkflowWithChat() {
109114
[workspaceTabs]
110115
)
111116
const stageActiveId = workspaceTabs?.activeTabId ?? null
117+
const activeStageTab = stageTabs.find((tab) => tab.id === stageActiveId) ?? stageTabs[0]
112118

113119
const stageResource = useCallback(
114120
(resource: MothershipResource) => {
115121
if (!workspaceId) return
116122
openTabs(workspaceId, [resource], { focusId: resource.id })
117123
setStackOpen(true)
124+
setStageFront('card')
118125
reflectParam('resource', resource.id)
119126
},
120127
[openTabs, workspaceId, reflectParam]
121128
)
122129

123130
const collapseStack = useCallback(() => {
124131
setStackOpen(false)
132+
setStageFront('card')
125133
reflectParam('resource', null)
126134
}, [reflectParam])
127135

@@ -155,14 +163,15 @@ export function WorkflowWithChat() {
155163
return useMothershipTabsStore.persist.onFinishHydration(apply)
156164
}, [workspaceId, setActiveTab, openTabs])
157165

166+
/** Escape flips the editor forward (the stack stays one click away). */
158167
useEffect(() => {
159-
if (!stackOpen) return
168+
if (!stackOpen || stageFront !== 'card') return
160169
const onKeyDown = (event: KeyboardEvent) => {
161-
if (event.key === 'Escape') collapseStack()
170+
if (event.key === 'Escape') setStageFront('editor')
162171
}
163172
window.addEventListener('keydown', onKeyDown)
164173
return () => window.removeEventListener('keydown', onKeyDown)
165-
}, [stackOpen, collapseStack])
174+
}, [stackOpen, stageFront])
166175

167176
/**
168177
* Closing the last tab leaves nothing to show — the editor comes forward.
@@ -310,7 +319,7 @@ export function WorkflowWithChat() {
310319
workflowId={workflowId}
311320
chatDock={{ isOpen: dock.open, onSelectChat: openChat }}
312321
/>
313-
{stackOpen && (
322+
{stackOpen && stageFront === 'card' && (
314323
<>
315324
{/* Opaque stage backdrop: the editor stays mounted and live
316325
underneath, but the back card shows only its identity — never
@@ -323,19 +332,19 @@ export function WorkflowWithChat() {
323332
<button
324333
type='button'
325334
aria-label='Back to workflow'
326-
onClick={collapseStack}
327-
className='absolute inset-x-4 top-2 z-30 flex h-[52px] items-start rounded-t-xl border border-[var(--border-1)] bg-[var(--surface-5)] px-3 transition-colors hover-hover:bg-[var(--surface-active)] dark:bg-[var(--surface-4)]'
335+
onClick={() => setStageFront('editor')}
336+
className='absolute inset-x-4 top-2 z-30 flex h-[38px] items-start rounded-t-lg border border-[var(--border-1)] bg-[var(--bg)] px-3 transition-colors hover-hover:bg-[var(--surface-active)]'
328337
>
329-
<span className='flex h-[42px] min-w-0 items-center gap-1.5'>
330-
<WorkflowIcon className='size-[14px] flex-shrink-0 text-[var(--text-icon)]' />
331-
<span className='truncate font-medium text-[14px] text-[var(--text-body)]'>
338+
<span className='flex h-[26px] min-w-0 items-center gap-1.5'>
339+
<WorkflowIcon className='size-[12px] flex-shrink-0 text-[var(--text-icon)]' />
340+
<span className='truncate font-medium text-[12px] text-[var(--text-body)]'>
332341
{workflowName}
333342
</span>
334343
</span>
335344
</button>
336345
{/* The front card: the workspace resource tabs + active content,
337346
a fully detached rounded pane over the back card. */}
338-
<div className='absolute inset-x-2 top-[46px] bottom-2 z-30 flex animate-slide-in-bottom flex-col overflow-hidden rounded-xl border border-[var(--border-1)] bg-[var(--bg)] shadow-sm'>
347+
<div className='absolute inset-x-2 top-[32px] bottom-2 z-30 flex animate-slide-in-bottom flex-col overflow-hidden rounded-xl border border-[var(--border-1)] bg-[var(--bg)] shadow-sm'>
339348
<MothershipResourcesProvider
340349
selectResource={selectStageTab}
341350
addResource={addStageTab}
@@ -364,12 +373,38 @@ export function WorkflowWithChat() {
364373
</button>
365374
</Tooltip.Trigger>
366375
<Tooltip.Content side='bottom'>
367-
<p>Back to workflow</p>
376+
<p>Close resources</p>
368377
</Tooltip.Content>
369378
</Tooltip.Root>
370379
</div>
371380
</>
372381
)}
382+
{stackOpen && stageFront === 'editor' && activeStageTab && (
383+
/* The resource card tucked at the bottom edge while the editor is
384+
forward — same identity-bar treatment as the workflow's, so the
385+
two sides of the stack stay one click apart. */
386+
<button
387+
type='button'
388+
aria-label='Show resources'
389+
onClick={() => setStageFront('card')}
390+
className='absolute right-4 bottom-0 z-30 flex h-[32px] items-start rounded-t-lg border border-[var(--border-1)] border-b-0 bg-[var(--bg)] px-3 shadow-sm transition-colors hover-hover:bg-[var(--surface-active)]'
391+
>
392+
<span className='flex h-[30px] min-w-0 items-center gap-1.5'>
393+
{getResourceConfig(activeStageTab.type).renderTabIcon(
394+
activeStageTab,
395+
'size-[12px] flex-shrink-0 text-[var(--text-icon)]'
396+
)}
397+
<span className='max-w-[200px] truncate font-medium text-[12px] text-[var(--text-body)]'>
398+
{activeStageTab.title}
399+
</span>
400+
{stageTabs.length > 1 && (
401+
<span className='text-[11px] text-[var(--text-muted)]'>
402+
+{stageTabs.length - 1}
403+
</span>
404+
)}
405+
</span>
406+
</button>
407+
)}
373408
</div>
374409
</div>
375410
)

0 commit comments

Comments
 (0)