Skip to content

Commit 7545391

Browse files
authored
feat(docs): render workflow previews with the shared editor renderer (#5277)
* chore(workflow-renderer): declare @sim/emcn dep + wire the package into docs Adds the missing @sim/emcn peer/dev dependency to @sim/workflow-renderer (it imports @sim/emcn in every View but resolved only via workspace hoisting). Wires apps/docs to consume @sim/workflow-renderer (dependency, transpilePackages, Tailwind @source) and adds remark-breaks (pulled transitively via the barrel's NoteBlockView export) — mirroring the @sim/emcn integration. Foundation for migrating the docs workflow-preview fork onto the shared Views. Build resolves the package/@source/remark-breaks cleanly. * feat(docs): render loop/parallel containers with the shared SubflowNodeView Replaces the forked PreviewContainerNode with a thin DocsContainerNode that maps the static preview data to SubflowNodeView's read-only (isPreview) props — no stores or hooks. Adds the block size to the preview node data so the view can size itself, and corrects the parallel example's start-edge handle id to 'parallel-start-source' (the view derives the handle id from kind). Deletes preview-container-node.tsx. Container colors/icons are now owned by the shared view (loop=blue, parallel=yellow). * feat(docs): render block nodes with the shared WorkflowBlockView Replaces the forked PreviewBlockNode with a thin DocsBlockNode that maps the static preview data to WorkflowBlockView's props — store-free, builds the subblock rows (condition/router Context+routes/default + tools + error) via SubBlockRowView, strips branch-id prefixes so the view's regenerated handle ids match, remaps router->router_v2, and keeps the framer-motion dim/stagger wrapper. Promotes resolveIcon into block-icons.tsx, adds the --workflow-edge token to docs global.css, deletes preview-block-node.tsx. The canvas diagrams now render with the real editor's view. * refactor(workflow-renderer): make editor-only WorkflowBlockView props optional The child-deploy, schedule, and webhook badge props (and their callbacks) only matter in the editor. Mark them optional and optional-chain the three callbacks so read-only consumers (docs, academy) can omit the whole group instead of passing ~18 explicit off-values. The editor still passes them, so its behavior is byte-identical (verified: apps/sim type-check clean). DocsBlockNode drops the off-props. * feat(docs): replace how-it-runs static diagrams with live WorkflowPreview Swaps the four static PNGs on the how-it-runs page for live, app-styled WorkflowPreview diagrams (concurrency, combination, condition+router branching, error path). Adds the four example workflows and renders error-port edges red to match the editor. The English page only; the translated execution/basics pages keep the PNGs. * refactor(workflow-renderer): the view owns condition/router/error rows Both the editor container and the docs adapter hand-built the condition/router/error summary rows in an order that had to stay in lockstep with the view's absolute handle-offset math — a three-way coupling with nothing enforcing it. The view now renders those rows itself from the conditionRows/routerRows it already receives (plus a routerContextValue prop for the router's Context row), so row order and handle geometry live together in one place. Both containers pass only data and their non-branch rows. Editor is byte-identical: getDisplayValue moves to where conditionRows/routerRows are built; the no-subBlock SubBlockRow path is already an exact SubBlockRowView(title, value) passthrough; the error row stays gated on shouldShowDefaultHandles. Verified apps/sim type-check clean. Docs now also renders the error row on condition/router blocks, which the real editor already did (shouldShowDefaultHandles is true for them) — an alignment fix. * refactor(docs): drop the parallel --wp-* token layer for the app/emcn tokens The workflow-preview ran a 25-token --wp-* mirror (22 were pure aliases of app tokens docs already defines) plus a .wp-scope wrapper class. Replaces every var(--wp-X) with its canonical app/emcn token (--wp-edge->--workflow-edge, --wp-highlight->--brand-secondary, badges->--badge-*, etc.), adds the one missing token (--divider), and deletes the .wp-scope blocks + class. Visually identical (aliases resolve to the same values); the preview now inherits the same design tokens as the shared views and the rest of the app instead of a hand-rolled parallel set. * refactor(docs): adopt emcn Badge + dedup resolveIcon in workflow-preview output-bundle's hand-rolled type badge (BADGE_COLORS + a styled span) becomes the emcn Badge (its green/blue/orange/purple/gray variants use the identical --badge-* tokens). resolveIcon, which had three copies, is now imported once from block-icons by output-bundle and block-inspector. * refactor(docs): rebuild the preview inspector on emcn chip primitives The lightbox inspector was a hand-rolled facsimile (raw divs + a CONTROL class string + inline dashed borders). It now composes from the same @sim/emcn primitives the live editor's sub-block controls wrap — ChipSelect/ChipInput/ChipTextarea(viewOnly)/ChipSwitch/ChipTag/FieldDivider/Label — so it reads as the real editor panel, fed example data (read-only, full opacity via readOnly/viewOnly, not greyed). Slider stays minimal (no emcn equivalent) but on app tokens. Props API and embedded/standalone modes unchanged. * refactor(docs): render the block-reference hero through the shared View Retires the hand-rolled BlockCard (a parallel reimplementation of WorkflowBlockView) and the BlockDisplaySpec data model. Each block hero is now a single-block PreviewWorkflow (block-display-workflows.ts) rendered through the same toReactFlowElements -> DocsBlockNode -> WorkflowBlockView pipeline as the diagrams, mounted in a minimal fitView ReactFlow (maxZoom 1.3, no canvas chrome). A single block can no longer drift from the canvas. * fix(docs): define sim's type scale + align the preview inspector to the editor Docs Tailwind v4 never defined sim's custom font sizes (text-small/caption/md/micro), so emcn components (Label, Badge, the shared views) fell back to inherited sizes — the inspector labels rendered huge. Adds the type scale to the docs @theme. Also aligns the inspector header to the real editor panel (surface-4 bar, size-[18px] rounded-sm icon, text-sm name) and removes the Connections section (and its now-dead prop/wiring). * fix(docs): inspector shows the full field list + dragged positions persist Inspector: shows the block type's full field list (from the reference data) with the example's values overlaid, so it reads like the editor panel instead of only the canvas summary rows. Drag: selecting another block no longer relayouts the canvas — node positions the viewer dragged are preserved across highlight/selection changes (only a different workflow relayouts). * feat(docs): highlight <> references + env vars; hide Ask AI over the lightbox; respace blocks Inspector text fields render the value with <...> block references and {{...}} environment variables highlighted in brand-secondary (a lean read-only port of the editor's formatDisplayText), in the canonical chip field chrome. The floating Ask AI widget is hidden while a preview lightbox is open. Plus the example-data respacing so the editor-faithful Error row no longer makes stacked blocks overlap. * fix(docs): make per-type field templates match the real block registry Audited every block type's field list (the source the inspector + block-reference heroes render) against apps/sim/blocks/blocks/*. Corrected drift to the registry's default-visible fields, titles, and order: agent gains Temperature; router gains Model; wait gains Async; schedule rewritten (default is Daily, not minutes); webhook_trigger expanded to its real default-visible set; human_in_the_loop notification title fixed. Provider-credential and advanced-mode fields stay hidden, matching the editor. Canvas diagrams keep their clean curated rows; the inspector now shows the full, real field list per the chosen clean-canvas/full-inspector split. * improvement(docs): taller default preview height so respaced diagrams aren't shrunk Bumps the default WorkflowPreview height 260->300 (the respaced, editor-faithful blocks are taller, so fitView was shrinking diagrams that relied on the default). The tall how-it-runs routing diagram gets 400. * improvement(docs): zoomable inline preview + taller default + themed controls The inline preview is now zoomable outside the lightbox: adds react-flow zoom/fit Controls (themed to the dark canvas chrome) and enables pinch-zoom, while keeping scroll-zoom off so the page still scrolls over the diagram. Pan-drag and click-block-to-inspect already worked. Default height 300->340. * improvement(docs): click canvas to expand; click empty lightbox to deselect Clicking the inline preview canvas opens the full lightbox; clicking empty space in the lightbox clears the selection, matching the real editor. * improvement(docs): reveal inline zoom controls on hover only The always-visible zoom controls felt heavy on the inline preview; they now fade in on hover (matching the expand button) and stay visible in the lightbox. * improvement(docs): drop zoom controls on the inline preview Inline preview keeps pinch-zoom, pan, drag, and click-to-expand; zoom buttons stay in the lightbox only. * improvement(docs): remove zoom controls from the lightbox too Both previews zoom via scroll/pinch and pan via drag; no on-canvas zoom buttons. Drops the Controls import and its theming CSS. * improvement(docs): match the real canvas — flat background + editor edge geometry Closes the last faithfulness gaps the audit found: removes the dot grid (the real editor hides its background — flat bg), aligns PreviewEdge to the editor's smoothstep math (borderRadius 8, offset 30) and 2px stroke (default + error edges), the selection ring to 1.75px, and minZoom to 0.1. Structural parity (blocks/handles/containers/colors/tokens) was already shared. Kept PreviewEdge rather than swapping to WorkflowEdgeView, which would clobber the docs-only highlight/dim/animate for no visual gain. * improvement(docs): rebrand the docs assistant as 'Ask Sim', styled like the real chat input Renames the floating assistant from 'Ask AI' to 'Ask Sim' (matching the platform's voice — you talk to Sim) and restyles the composer to mirror the home chat input: a rounded-2xl bordered field with the toolbar inside, and the same 28px circular send/stop button (the home's exact active/disabled colors + white/black arrow). Updates the lightbox hide-selector to the new label. * improvement(docs): match Ask Sim message styling to the mothership chat Aligns the user bubble (rounded-[16px] surface-5, text-base/primary, leading-23, max-w-85%) and the assistant markdown (text-base, 600 headings/strong, text-primary dashed-underline links, surface-5 code blocks) to the real mothership chat's user-message + chat-content treatment, instead of the prior generic text-sm rendering. The composer already mirrors the home user-input (rounded-2xl field + 28px circular send button). * improvement(docs): compact single-row Ask Sim composer The two-row layout left a tall dead gap (the docs widget has no toolbar buttons to fill the second row). The composer is now a single row — textarea with the circular send button inline — so it sits at the natural input height. * fix(docs): pass the router Context value to the shared view DocsBlockNode never set routerContextValue, so the view (which renders the router's Context row from that prop, not from rows) showed a blank Context even when the preview data authored a value like <start.input>. Extract it from the block's Context row and pass it through. * fix(docs): don't apply a block-type field template that doesn't match the block inspectorFieldsFor keyed the full field template purely off block.type, but some types are reused across roles (a table action block vs the table trigger, a webhook trigger vs the webhook action), so the wrong template was applied. Only use the template when the block's authored rows are actually a subset of it; otherwise fall back to the block's own rows. * fix(docs): connect preview edges to subflow container handles toReactFlowElements hardcoded targetHandle to 'target' and defaulted source handles to 'source', but Loop/Parallel containers (SubflowNodeView) expose a 'loop-end-source'/'parallel-end-source' output handle and a left input handle with no id. Edges into and out of containers therefore failed to connect. Resolve each edge end to the block's real handle based on whether it's a container. * fix(docs): don't expand the inspector template for blocks with no rows block.rows.every(...) is vacuously true for an empty rows array, so a block defined only by branches (e.g. a router in ROUTING_WORKFLOW) inherited the type template's invented field defaults. Require non-empty authored rows before applying the template. * fix(docs): render blank branch/router-context values as '-' like the editor The editor maps condition/router branch values and the router Context through getDisplayValue, which renders '-' for a blank value. DocsBlockNode mapped them to an empty string, so else branches and unset routes looked blank instead of matching the editor. Mirror getDisplayValue's empty-value handling. * fix(docs): show '-' for blank inspector branch values, matching the canvas inspectorFieldsFor passed raw branch.value into the lightbox branch fields, so an unset else route read blank in the inspector while DocsBlockNode (and the editor's getDisplayValue) render '-' on the canvas. Normalize the same way; drop the now-redundant placeholder.
1 parent f66d0c7 commit 7545391

24 files changed

Lines changed: 1291 additions & 1052 deletions

apps/docs/app/global.css

Lines changed: 19 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
@import "fumadocs-ui/css/preset.css";
44
@import "fumadocs-openapi/css/preset.css";
55
@source "../../../packages/emcn/src";
6+
@source "../../../packages/workflow-renderer/src";
67

78
/* Prevent overscroll bounce effect on the page */
89
html,
@@ -19,6 +20,20 @@ body {
1920

2021
@theme {
2122
--color-fd-primary: var(--color-fd-foreground);
23+
/* Sim's custom type scale — emcn components (Label, Badge, the shared block
24+
views) use these names, so they must resolve here or text falls back to the
25+
inherited size. Mirrors apps/sim/tailwind.config.ts. */
26+
--text-micro: 10px;
27+
--text-xs: 11px;
28+
--text-caption: 12px;
29+
--text-small: 13px;
30+
--text-base: 15px;
31+
--text-md: 16px;
32+
}
33+
34+
/* Hide the floating Ask Sim widget while a workflow-preview lightbox is open. */
35+
body.wp-lightbox-open [aria-label="Ask Sim"] {
36+
display: none;
2237
}
2338

2439
/* Pure white light mode background */
@@ -42,6 +57,7 @@ body {
4257
*/
4358
:root {
4459
--bg: #fefefe;
60+
--workflow-edge: #e0e0e0;
4561
--surface-1: #fbfbfb;
4662
--surface-2: #ffffff;
4763
--surface-3: #f7f7f7;
@@ -51,6 +67,7 @@ body {
5167
--surface-active: #ececec;
5268
--border: #dedede;
5369
--border-1: #e0e0e0;
70+
--divider: #ededed;
5471
--text-primary: #1a1a1a;
5572
--text-secondary: #525252;
5673
--text-tertiary: #5c5c5c;
@@ -91,6 +108,7 @@ body {
91108

92109
.dark {
93110
--bg: #1b1b1b;
111+
--workflow-edge: #454545;
94112
--surface-1: #1e1e1e;
95113
--surface-2: #232323;
96114
--surface-3: #242424;
@@ -100,6 +118,7 @@ body {
100118
--surface-active: #2c2c2c;
101119
--border: #333333;
102120
--border-1: #3d3d3d;
121+
--divider: #393939;
103122
--text-primary: #e6e6e6;
104123
--text-secondary: #cccccc;
105124
--text-tertiary: #b3b3b3;
@@ -1661,56 +1680,6 @@ main article blockquote {
16611680
box-shadow: none !important;
16621681
}
16631682

1664-
/* Workflow-preview theme scope. Values mirror the app's tokens in
1665-
apps/sim/app/_styles/globals.css (light from :root/.light, dark from .dark)
1666-
so the docs previews match the OG repository in both modes. */
1667-
.wp-scope {
1668-
/* surfaces */
1669-
--wp-canvas: var(--bg);
1670-
--wp-panel: var(--surface-1);
1671-
--wp-surface: var(--surface-2);
1672-
--wp-header: var(--surface-3);
1673-
--wp-btn: var(--surface-4);
1674-
--wp-control: var(--surface-5);
1675-
--wp-active: var(--surface-active);
1676-
--wp-container-fill: rgba(0, 0, 0, 0.02);
1677-
1678-
/* borders */
1679-
--wp-border: var(--border);
1680-
--wp-border-1: var(--border-1);
1681-
--wp-chip-bg: var(--surface-5); /* ChipTag surface (light) */
1682-
--wp-chip-text: var(--text-body);
1683-
--wp-divider: #ededed; /* --divider */
1684-
--wp-edge: #e0e0e0; /* --workflow-edge */
1685-
--wp-highlight: #33b4ff; /* traced edge / highlighted output node */
1686-
1687-
/* text */
1688-
--wp-text: var(--text-primary);
1689-
--wp-text-2: var(--text-secondary);
1690-
--wp-text-3: var(--text-tertiary);
1691-
--wp-text-muted: var(--text-muted);
1692-
--wp-text-subtle: var(--text-subtle);
1693-
1694-
/* type badges (output inspector) */
1695-
--wp-badge-success-bg: var(--badge-success-bg);
1696-
--wp-badge-success-text: var(--badge-success-text);
1697-
--wp-badge-blue-bg: var(--badge-blue-bg);
1698-
--wp-badge-blue-text: var(--badge-blue-text);
1699-
--wp-badge-orange-bg: var(--badge-orange-bg);
1700-
--wp-badge-orange-text: var(--badge-orange-text);
1701-
--wp-badge-purple-bg: var(--badge-purple-bg);
1702-
--wp-badge-purple-text: var(--badge-purple-text);
1703-
--wp-badge-gray-bg: var(--badge-gray-bg);
1704-
--wp-badge-gray-text: var(--badge-gray-text);
1705-
}
1706-
1707-
.dark .wp-scope {
1708-
--wp-container-fill: rgba(255, 255, 255, 0.02);
1709-
--wp-chip-bg: var(--surface-4); /* ChipTag surface (dark) */
1710-
--wp-divider: #393939;
1711-
--wp-edge: #454545;
1712-
}
1713-
17141683
/* Tailwind v4 content sources */
17151684
@source '../app/**/*.{js,ts,jsx,tsx,mdx}';
17161685
@source '../components/**/*.{js,ts,jsx,tsx,mdx}';

apps/docs/components/ai/ask-ai.tsx

Lines changed: 56 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -82,12 +82,12 @@ export function AskAI({ locale }: AskAIProps) {
8282
{!open && (
8383
<button
8484
type='button'
85-
aria-label='Ask AI'
85+
aria-label='Ask Sim'
8686
onClick={() => setOpen(true)}
8787
className='fixed right-4 bottom-4 z-50 flex h-11 items-center gap-1.5 rounded-full border border-[var(--border-1)] bg-[var(--surface-5)] px-4 font-season text-[var(--text-body)] text-sm shadow-[var(--shadow-medium)] transition-colors hover:bg-[var(--surface-active)] dark:bg-[var(--surface-4)]'
8888
>
8989
<MessageCircle className='size-[16px] text-[var(--text-icon)]' />
90-
Ask AI
90+
Ask Sim
9191
</button>
9292
)}
9393

@@ -96,7 +96,7 @@ export function AskAI({ locale }: AskAIProps) {
9696
<div className='flex items-center justify-between border-[var(--border-1)] border-b px-4 py-3'>
9797
<span className='flex items-center gap-1.5 font-season text-[var(--text-body)] text-sm'>
9898
<MessageCircle className='size-[16px] text-[var(--text-icon)]' />
99-
Ask AI
99+
Ask Sim
100100
</span>
101101
<button
102102
type='button'
@@ -127,18 +127,27 @@ export function AskAI({ locale }: AskAIProps) {
127127
<div
128128
key={message.id}
129129
className={cn(
130-
'flex flex-col gap-1',
130+
'flex flex-col gap-1.5',
131131
message.role === 'user' ? 'items-end' : 'items-start'
132132
)}
133133
>
134134
{message.role === 'user' ? (
135-
<div className='max-w-[90%] whitespace-pre-wrap rounded-lg bg-[var(--surface-active)] px-3 py-2 text-[var(--text-body)] text-sm'>
135+
<div className='max-w-[85%] whitespace-pre-wrap rounded-[16px] bg-[var(--surface-5)] px-3 py-2 text-[var(--text-primary)] text-base leading-[23px]'>
136136
{text}
137137
</div>
138138
) : (
139-
<div className='max-w-[90%] text-[var(--text-body)] text-sm'>
139+
<div className='max-w-full text-[var(--text-primary)] text-base'>
140140
{text ? (
141-
<Streamdown className='space-y-2 text-sm leading-relaxed [&_a]:text-[var(--brand-accent)] [&_a]:underline [&_li]:my-0.5 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:list-disc [&_ul]:pl-5'>
141+
<Streamdown
142+
className={cn(
143+
'space-y-3 text-[var(--text-primary)] text-base leading-relaxed',
144+
'[&_a]:text-[var(--text-primary)] [&_a]:underline [&_a]:decoration-dashed [&_a]:underline-offset-4',
145+
'[&_strong]:font-[600]',
146+
'[&_h1]:font-[600] [&_h2]:font-[600] [&_h3]:font-[600] [&_h4]:font-[600]',
147+
'[&_li]:my-1 [&_ol]:my-3 [&_ol]:list-decimal [&_ol]:pl-5 [&_ul]:my-3 [&_ul]:list-disc [&_ul]:pl-5',
148+
'[&_code]:font-mono [&_pre]:my-3 [&_pre]:overflow-x-auto [&_pre]:rounded-lg [&_pre]:bg-[var(--surface-5)] [&_pre]:p-3 [&_pre]:text-small'
149+
)}
150+
>
142151
{text}
143152
</Streamdown>
144153
) : isStreaming ? (
@@ -174,42 +183,46 @@ export function AskAI({ locale }: AskAIProps) {
174183
)}
175184
</div>
176185

177-
<form
178-
onSubmit={handleSubmit}
179-
className='flex items-end gap-2 border-[var(--border-1)] border-t px-3 py-3'
180-
>
181-
<textarea
182-
value={input}
183-
onChange={(event) => setInput(event.target.value)}
184-
onKeyDown={(event) => {
185-
if (event.key === 'Enter' && !event.shiftKey) {
186-
event.preventDefault()
187-
handleSubmit(event)
188-
}
189-
}}
190-
rows={1}
191-
placeholder='Ask a question…'
192-
className='max-h-32 flex-1 resize-none bg-transparent font-season text-[var(--text-body)] text-sm outline-none placeholder:text-[var(--text-muted)]'
193-
/>
194-
{isBusy ? (
195-
<button
196-
type='button'
197-
aria-label='Stop'
198-
onClick={() => stop()}
199-
className='flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--surface-active)] text-[var(--text-icon)]'
200-
>
201-
<Square className='size-[16px]' />
202-
</button>
203-
) : (
204-
<button
205-
type='submit'
206-
aria-label='Send'
207-
disabled={!input.trim()}
208-
className='flex size-8 shrink-0 items-center justify-center rounded-lg bg-[var(--text-primary)] text-[var(--text-inverse)] transition-opacity disabled:opacity-40 dark:bg-white dark:text-[var(--bg)]'
209-
>
210-
<ArrowUp className='size-[16px]' />
211-
</button>
212-
)}
186+
<form onSubmit={handleSubmit} className='px-3 pb-3'>
187+
<div className='flex items-end gap-2 rounded-2xl border border-[var(--border-1)] bg-white px-2.5 py-1.5 dark:bg-[var(--surface-5)]'>
188+
<textarea
189+
value={input}
190+
onChange={(event) => setInput(event.target.value)}
191+
onKeyDown={(event) => {
192+
if (event.key === 'Enter' && !event.shiftKey) {
193+
event.preventDefault()
194+
handleSubmit(event)
195+
}
196+
}}
197+
rows={1}
198+
placeholder='Ask Sim about the docs…'
199+
className='max-h-32 flex-1 resize-none bg-transparent py-1 font-season text-[var(--text-body)] text-sm outline-none placeholder:text-[var(--text-muted)]'
200+
/>
201+
{isBusy ? (
202+
<button
203+
type='button'
204+
aria-label='Stop'
205+
onClick={() => stop()}
206+
className='flex size-[28px] shrink-0 items-center justify-center rounded-full bg-[#383838] transition-colors hover:bg-[#575757] dark:bg-[#e0e0e0] dark:hover:bg-[#cfcfcf]'
207+
>
208+
<Square className='size-[12px] fill-white text-white dark:fill-black dark:text-black' />
209+
</button>
210+
) : (
211+
<button
212+
type='submit'
213+
aria-label='Send'
214+
disabled={!input.trim()}
215+
className={cn(
216+
'flex size-[28px] shrink-0 items-center justify-center rounded-full transition-colors',
217+
input.trim()
218+
? 'bg-[#383838] hover:bg-[#575757] dark:bg-[#e0e0e0] dark:hover:bg-[#cfcfcf]'
219+
: 'bg-[#808080]'
220+
)}
221+
>
222+
<ArrowUp className='size-[16px] text-white dark:text-black' />
223+
</button>
224+
)}
225+
</div>
213226
</form>
214227
</div>
215228
)}

0 commit comments

Comments
 (0)