Skip to content

Commit 2e310af

Browse files
d34dmanclaude
andcommitted
docs: add live interrupt prompt previews to Human-in-the-Loop guide
Add interactive iframe-based previews for Confirmation, Choice, Text Input, and Form interrupt types, mirroring the pattern used for node type previews. Replace ASCII architecture diagram with a mermaid sequence diagram. Export ReviewPrompt from playground module for future use. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d30a3e4 commit 2e310af

7 files changed

Lines changed: 337 additions & 6 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<script lang="ts">
2+
interface Props {
3+
type: string;
4+
height?: string;
5+
}
6+
7+
let { type, height = '280px' }: Props = $props();
8+
9+
// Detect the current Starlight theme and pass it to the preview iframe
10+
let theme = $state('dark');
11+
12+
$effect(() => {
13+
const updateTheme = () => {
14+
theme = document.documentElement.dataset.theme === 'light' ? 'light' : 'dark';
15+
};
16+
17+
updateTheme();
18+
19+
const observer = new MutationObserver(updateTheme);
20+
observer.observe(document.documentElement, {
21+
attributes: true,
22+
attributeFilter: ['data-theme']
23+
});
24+
25+
return () => observer.disconnect();
26+
});
27+
28+
const previewUrl = $derived(`/interrupt-preview/?type=${type}&theme=${theme}`);
29+
</script>
30+
31+
<div class="fd-demo-inline fd-interrupt-preview" style:height>
32+
<iframe
33+
src={previewUrl}
34+
class="fd-demo-iframe"
35+
title="FlowDrop {type} interrupt preview"
36+
loading="lazy"
37+
></iframe>
38+
</div>
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
<!--
2+
InterruptPreviewRenderer: Renders a single interrupt prompt component.
3+
Used inside the interrupt-preview.astro iframe page.
4+
-->
5+
<script lang="ts">
6+
import {
7+
ConfirmationPrompt,
8+
ChoicePrompt,
9+
TextInputPrompt,
10+
FormPrompt,
11+
} from '@flowdrop/flowdrop/playground';
12+
import '@flowdrop/flowdrop/styles';
13+
14+
let {
15+
type,
16+
data
17+
}: {
18+
type: string;
19+
data: Record<string, unknown>;
20+
} = $props();
21+
22+
// No-op handlers for preview mode
23+
const noop = () => {};
24+
const noopSubmit = (_v: unknown) => {};
25+
</script>
26+
27+
<div class="interrupt-preview-wrapper">
28+
{#if type === 'confirmation'}
29+
<ConfirmationPrompt
30+
config={data.config}
31+
isResolved={false}
32+
isSubmitting={false}
33+
onConfirm={noop}
34+
onDecline={noop}
35+
/>
36+
{:else if type === 'choice'}
37+
<ChoicePrompt
38+
config={data.config}
39+
isResolved={false}
40+
isSubmitting={false}
41+
onSubmit={noopSubmit}
42+
/>
43+
{:else if type === 'text_input'}
44+
<TextInputPrompt
45+
config={data.config}
46+
isResolved={false}
47+
isSubmitting={false}
48+
onSubmit={noopSubmit}
49+
/>
50+
{:else if type === 'form'}
51+
<FormPrompt
52+
config={data.config}
53+
isResolved={false}
54+
isSubmitting={false}
55+
onSubmit={noopSubmit}
56+
/>
57+
{:else}
58+
<p>Unknown interrupt type: {type}</p>
59+
{/if}
60+
</div>
61+
62+
<style>
63+
.interrupt-preview-wrapper {
64+
padding: 1.5rem;
65+
max-width: 600px;
66+
margin: 0 auto;
67+
}
68+
</style>

apps/docs/src/content/docs/guides/interrupts.md renamed to apps/docs/src/content/docs/guides/interrupts.mdx

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ title: Human-in-the-Loop
33
description: Pause workflows for human approval, input, or review.
44
---
55

6+
import InterruptPreview from '../../../components/InterruptPreview.svelte';
7+
68
FlowDrop's interrupt system enables workflows to pause execution and request user input before continuing. This is essential for approval workflows, data collection, decision points, and quality control.
79

810
## Interrupt Types
@@ -11,6 +13,8 @@ FlowDrop's interrupt system enables workflows to pause execution and request use
1113

1214
Simple yes/no prompt for binary decisions:
1315

16+
<InterruptPreview type="confirmation" height="200px" client:only="svelte" />
17+
1418
```json
1519
{
1620
"interrupt_type": "confirmation",
@@ -26,6 +30,8 @@ Simple yes/no prompt for binary decisions:
2630

2731
Single or multiple selection from predefined options:
2832

33+
<InterruptPreview type="choice" height="360px" client:only="svelte" />
34+
2935
```json
3036
{
3137
"interrupt_type": "choice",
@@ -44,6 +50,8 @@ Single or multiple selection from predefined options:
4450

4551
Free-form text entry:
4652

53+
<InterruptPreview type="text_input" height="340px" client:only="svelte" />
54+
4755
```json
4856
{
4957
"interrupt_type": "text_input",
@@ -60,6 +68,8 @@ Free-form text entry:
6068

6169
Complex data entry using JSON Schema:
6270

71+
<InterruptPreview type="form" height="520px" client:only="svelte" />
72+
6373
```json
6474
{
6575
"interrupt_type": "form",
@@ -106,12 +116,19 @@ Review proposed field changes with per-field accept/reject decisions and visual
106116

107117
## Architecture
108118

109-
```
110-
Workflow Execution -> Backend (creates interrupt) -> Frontend (renders prompt)
111-
|
112-
User Response
113-
|
114-
Backend (resumes) -> Workflow continues
119+
```mermaid
120+
sequenceDiagram
121+
participant W as Workflow Execution
122+
participant B as Backend
123+
participant F as Frontend
124+
participant U as User
125+
126+
W->>B: Pause & create interrupt
127+
B->>F: Send interrupt request
128+
F->>U: Render prompt
129+
U->>F: Submit response
130+
F->>B: Resolve interrupt
131+
B->>W: Resume workflow
115132
```
116133

117134
## Frontend Integration
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/**
2+
* Mock data for interrupt prompt previews in the docs.
3+
* Each entry contains the config props needed by the corresponding prompt component.
4+
*/
5+
6+
export const interruptPreviewData: Record<string, Record<string, unknown>> = {
7+
confirmation: {
8+
config: {
9+
message: 'Do you approve sending this email to 150 recipients?',
10+
confirmLabel: 'Yes, send email',
11+
cancelLabel: 'No, cancel'
12+
},
13+
isResolved: false,
14+
resolvedValue: undefined,
15+
isSubmitting: false,
16+
error: undefined,
17+
resolvedByUserName: undefined
18+
},
19+
20+
choice: {
21+
config: {
22+
message: 'Select the output format:',
23+
options: [
24+
{ value: 'json', label: 'JSON', description: 'Structured data' },
25+
{ value: 'csv', label: 'CSV', description: 'Spreadsheet format' },
26+
{ value: 'xml', label: 'XML', description: 'Markup language' }
27+
],
28+
multiple: false
29+
},
30+
isResolved: false,
31+
resolvedValue: undefined,
32+
isSubmitting: false,
33+
error: undefined,
34+
resolvedByUserName: undefined
35+
},
36+
37+
text_input: {
38+
config: {
39+
message: 'Provide additional context for this workflow step:',
40+
placeholder: 'Enter your notes...',
41+
multiline: true,
42+
maxLength: 500
43+
},
44+
isResolved: false,
45+
resolvedValue: undefined,
46+
isSubmitting: false,
47+
error: undefined,
48+
resolvedByUserName: undefined
49+
},
50+
51+
form: {
52+
config: {
53+
message: 'Complete the deployment configuration:',
54+
schema: {
55+
type: 'object',
56+
properties: {
57+
environment: {
58+
type: 'string',
59+
title: 'Environment',
60+
enum: ['development', 'staging', 'production']
61+
},
62+
notify: {
63+
type: 'boolean',
64+
title: 'Send notification'
65+
},
66+
version: {
67+
type: 'string',
68+
title: 'Version Tag'
69+
}
70+
}
71+
},
72+
defaultValues: {
73+
environment: 'staging',
74+
notify: true,
75+
version: '1.0.0'
76+
}
77+
},
78+
isResolved: false,
79+
resolvedValue: undefined,
80+
isSubmitting: false,
81+
error: undefined,
82+
resolvedByUserName: undefined
83+
},
84+
85+
review: {
86+
config: {
87+
message: 'Review these proposed changes:',
88+
changes: [
89+
{
90+
field: 'title',
91+
label: 'Page Title',
92+
original: 'About Us',
93+
proposed: 'About Our Company'
94+
},
95+
{
96+
field: 'description',
97+
label: 'Meta Description',
98+
original: 'Learn about our team.',
99+
proposed: 'Discover our company mission, values, and team.'
100+
}
101+
]
102+
},
103+
isResolved: false,
104+
resolvedValue: undefined,
105+
isSubmitting: false,
106+
error: undefined,
107+
resolvedByUserName: undefined
108+
}
109+
};
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
---
2+
/**
3+
* Standalone interrupt prompt preview page.
4+
* Renders a single FlowDrop interrupt prompt component.
5+
* Loaded in an iframe from the docs site for style isolation.
6+
* Query params (parsed client-side):
7+
* ?type=confirmation — interrupt type (confirmation|choice|text_input|form|review)
8+
* ?theme=dark — color theme (dark|light)
9+
*/
10+
---
11+
12+
<!doctype html>
13+
<html lang="en" data-theme="dark">
14+
<head>
15+
<meta charset="UTF-8" />
16+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
17+
<title>Interrupt Preview</title>
18+
<style is:global>
19+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
20+
html, body { width: 100%; height: 100%; overflow: auto; }
21+
body { font-family: system-ui, sans-serif; background: #0a0a0f; color: #c0c0d8; }
22+
#preview { width: 100%; }
23+
#loading {
24+
display: flex;
25+
align-items: center;
26+
justify-content: center;
27+
height: 100%;
28+
color: #7a7a9a;
29+
font-size: 0.875rem;
30+
}
31+
.hidden { display: none !important; }
32+
33+
/* Skin tokens for dark mode */
34+
[data-theme='dark'] {
35+
--fd-background: #13131a;
36+
--fd-foreground: #c0c0d8;
37+
--fd-muted: #1a1a28;
38+
--fd-muted-foreground: #7a7a9a;
39+
--fd-card: #1a1a28;
40+
--fd-card-foreground: #e8e8f0;
41+
--fd-border: #2a2a3a;
42+
--fd-border-muted: #1e1e2a;
43+
--fd-border-strong: #3a3a55;
44+
--fd-header: #1a1a25;
45+
--fd-layout-background: #0a0a0f;
46+
--fd-primary: #6c63ff;
47+
--fd-primary-hover: #7c74ff;
48+
--fd-primary-foreground: #ffffff;
49+
--fd-primary-muted: rgba(108, 99, 255, 0.15);
50+
--fd-subtle: rgba(108, 99, 255, 0.08);
51+
--fd-scrollbar-thumb: #2a2a3a;
52+
--fd-scrollbar-track: #13131a;
53+
--fd-backdrop: rgba(19, 19, 26, 0.9);
54+
--fd-success: #22c55e;
55+
--fd-success-muted: rgba(34, 197, 94, 0.12);
56+
--fd-success-foreground: #ffffff;
57+
--fd-error: #ef4444;
58+
--fd-error-muted: rgba(239, 68, 68, 0.12);
59+
--fd-error-foreground: #ffffff;
60+
--fd-warning: #f59e0b;
61+
}
62+
</style>
63+
</head>
64+
<body>
65+
<div id="loading">Loading...</div>
66+
<div id="preview" class="hidden"></div>
67+
68+
<script>
69+
(async () => {
70+
const params = new URLSearchParams(window.location.search);
71+
const interruptType = params.get('type') || 'confirmation';
72+
73+
const loadingEl = document.getElementById('loading');
74+
const previewEl = document.getElementById('preview');
75+
76+
try {
77+
const { mount } = await import('svelte');
78+
const { default: InterruptPreviewRenderer } = await import('../components/InterruptPreviewRenderer.svelte');
79+
const { interruptPreviewData } = await import('../mocks/interrupt-preview-data');
80+
81+
const data = interruptPreviewData[interruptType] || interruptPreviewData['confirmation'];
82+
83+
loadingEl.classList.add('hidden');
84+
previewEl.classList.remove('hidden');
85+
86+
mount(InterruptPreviewRenderer, {
87+
target: previewEl,
88+
props: { type: interruptType, data }
89+
});
90+
} catch (err) {
91+
console.error('Interrupt preview failed:', err);
92+
loadingEl.textContent = 'Failed to load preview';
93+
}
94+
})();
95+
</script>
96+
</body>
97+
</html>

libs/flowdrop/src/lib/components/interrupt/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ export { default as ConfirmationPrompt } from "./ConfirmationPrompt.svelte";
1515
export { default as ChoicePrompt } from "./ChoicePrompt.svelte";
1616
export { default as TextInputPrompt } from "./TextInputPrompt.svelte";
1717
export { default as FormPrompt } from "./FormPrompt.svelte";
18+
export { default as ReviewPrompt } from "./ReviewPrompt.svelte";

libs/flowdrop/src/lib/playground/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,7 @@ export {
133133
ChoicePrompt,
134134
TextInputPrompt,
135135
FormPrompt,
136+
ReviewPrompt,
136137
} from "../components/interrupt/index.js";
137138

138139
// ============================================================================

0 commit comments

Comments
 (0)