Skip to content

Commit 33c05f6

Browse files
committed
feat: demo improve modal copy
1 parent e917be1 commit 33c05f6

File tree

5 files changed

+371
-56
lines changed

5 files changed

+371
-56
lines changed

apps/demo/src/lib/components/CodePanel.svelte

Lines changed: 57 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import type { createFlowState } from '$lib/stores/pgflow-state-improved.svelte';
66
import StatusBadge from '$lib/components/StatusBadge.svelte';
77
import PulseDot from '$lib/components/PulseDot.svelte';
8+
import MiniDAG from '$lib/components/MiniDAG.svelte';
89
910
interface Props {
1011
flowState: ReturnType<typeof createFlowState>;
@@ -42,6 +43,21 @@
4243
return blocks;
4344
});
4445
46+
// Helper to trim common leading whitespace from code
47+
function trimLeadingWhitespace(code: string): string {
48+
const lines = code.split('\n');
49+
// Find minimum indentation (excluding empty lines)
50+
const minIndent = lines
51+
.filter((line) => line.trim().length > 0)
52+
.reduce((min, line) => {
53+
const indent = line.match(/^\s*/)?.[0].length ?? 0;
54+
return Math.min(min, indent);
55+
}, Infinity);
56+
57+
// Remove common leading whitespace
58+
return lines.map((line) => (line.trim().length > 0 ? line.slice(minIndent) : line)).join('\n');
59+
}
60+
4561
// Helper to get status for a step badge
4662
function getStepStatus(stepSlug: string): string | null {
4763
const status = flowState.stepStatuses[stepSlug];
@@ -94,8 +110,10 @@
94110
});
95111
96112
// Expanded code for explanation panel (mobile-selected)
113+
// Trim common leading whitespace for compact display
97114
const expandedCode = section.mobileCode || section.code;
98-
highlightedSectionsExpanded[slug] = await codeToHtml(expandedCode, {
115+
const trimmedCode = trimLeadingWhitespace(expandedCode);
116+
highlightedSectionsExpanded[slug] = await codeToHtml(trimmedCode, {
99117
lang: 'typescript',
100118
theme: 'night-owl'
101119
});
@@ -202,11 +220,18 @@
202220

203221
<div class="code-panel-wrapper">
204222
{#if isMobile && selectedStep}
205-
<!-- Mobile: Show only selected section in explanation panel (expanded version) -->
206-
<div class="code-panel mobile-selected">
207-
{#if highlightedSectionsExpanded[selectedStep]}
208-
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
209-
{@html highlightedSectionsExpanded[selectedStep]}
223+
<!-- Mobile: Show only selected section in explanation panel (expanded version) with optional mini DAG -->
224+
<div class="mobile-code-container">
225+
<div class="code-panel mobile-selected">
226+
{#if highlightedSectionsExpanded[selectedStep]}
227+
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
228+
{@html highlightedSectionsExpanded[selectedStep]}
229+
{/if}
230+
</div>
231+
{#if selectedStep !== 'flow_config'}
232+
<div class="mini-dag-container">
233+
<MiniDAG {selectedStep} />
234+
</div>
210235
{/if}
211236
</div>
212237
{:else if isMobile}
@@ -280,6 +305,19 @@
280305
position: relative;
281306
}
282307
308+
.mobile-code-container {
309+
display: flex;
310+
gap: 12px;
311+
align-items: center;
312+
}
313+
314+
.mini-dag-container {
315+
flex-shrink: 0;
316+
width: 95px;
317+
padding-right: 12px;
318+
opacity: 0.7;
319+
}
320+
283321
.code-panel {
284322
overflow-x: auto;
285323
border-radius: 5px;
@@ -288,21 +326,30 @@
288326
.code-panel.mobile-selected {
289327
/* Compact height when showing only selected step on mobile */
290328
min-height: auto;
291-
font-size: 13px;
329+
font-size: 12px;
292330
background: #0d1117;
293331
position: relative;
332+
flex: 1;
333+
}
334+
335+
.code-panel.mobile-selected :global(pre) {
336+
padding: 8px 0;
337+
}
338+
339+
.code-panel.mobile-selected :global(.line) {
340+
padding: 0 12px;
294341
}
295342
296343
.code-panel.mobile-sections {
297344
/* Mobile: Container for separate section blocks */
298-
font-size: 11px;
345+
font-size: 12px;
299346
border-radius: 0;
300347
}
301348
302349
/* Mobile: Smaller font, no border radius (touches edges) */
303350
@media (max-width: 768px) {
304351
.code-panel {
305-
font-size: 11px;
352+
font-size: 12px;
306353
border-radius: 0;
307354
}
308355
}
@@ -360,6 +407,7 @@
360407
background: #0d1117 !important;
361408
border-radius: 5px;
362409
line-height: 1.5;
410+
font-size: 13px; /* Desktop default */
363411
}
364412
365413
/* Mobile: Smaller padding */

apps/demo/src/lib/components/ExplanationPanel.svelte

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,33 @@
5454
steps: ['fetch_article', 'summarize', 'extract_keywords', 'publish']
5555
};
5656
57+
// Step-level concept explanations (how pgflow works internally)
58+
const stepConcepts: Record<string, string> = {
59+
fetch_article:
60+
"This is a root step with no dependencies. When start_flow() is called, SQL Core immediately " +
61+
"pushes a message to the queue. The Worker polls, gets the message, executes the handler, " +
62+
"and calls complete_task() with the return value. SQL Core acknowledges completion, saves the output, " +
63+
"and checks which dependent steps now have all their dependencies satisfied.",
64+
65+
summarize:
66+
"Depends on fetch_article. After fetch_article completes, SQL Core checks if this step's " +
67+
"dependencies are met. Since fetch_article is the only dependency, this step becomes ready " +
68+
"immediately. SQL Core pushes a message with the input payload (fetch_article's output). " +
69+
"Worker polls, executes the handler, calls complete_task(). SQL Core acknowledges completion and saves output.",
70+
71+
extract_keywords:
72+
"Also depends only on fetch_article, so it becomes ready at the same time as summarize. " +
73+
"Both messages hit the queue simultaneously - whichever Worker polls first starts execution. " +
74+
"This is how pgflow achieves parallel execution: SQL Core identifies ready steps and pushes messages, " +
75+
"Workers execute independently.",
76+
77+
publish:
78+
"Depends on both summarize AND extract_keywords. This step remains blocked until both " +
79+
"dependencies complete. After the second one finishes, complete_task() acknowledges completion, " +
80+
"saves output, checks dependencies, and finds publish is now ready. SQL Core pushes the message " +
81+
"with both outputs as input. After publish completes, no dependent steps remain - the run is marked completed."
82+
};
83+
5784
// Step metadata for explanation
5885
const stepInfo: Record<
5986
string,
@@ -299,6 +326,18 @@
299326
</p>
300327
</div>
301328

329+
<!-- Concept explainer (collapsible) -->
330+
<details class="concept-explainer">
331+
<summary
332+
class="font-semibold text-sm text-primary cursor-pointer hover:text-primary/80 flex items-center gap-2 mb-2"
333+
>
334+
<span>📚</span> How this step works in pgflow
335+
</summary>
336+
<div class="text-xs text-muted-foreground leading-relaxed bg-secondary/30 rounded p-3">
337+
{stepConcepts[currentStepInfo.name]}
338+
</div>
339+
</details>
340+
302341
<!-- Step-level view: 2-column on desktop, single column on mobile -->
303342
<div class="grid md:grid-cols-2 grid-cols-1 gap-4">
304343
<!-- Left Column: Dependencies -->
@@ -370,12 +409,40 @@
370409
{:else}
371410
<!-- Flow-level view -->
372411
<div class="space-y-3">
373-
<!-- What it does (highlighted) -->
412+
<!-- What it does -->
374413
<div class="bg-accent/30 rounded-lg p-3 border border-accent">
375-
<p class="text-foreground leading-relaxed mb-2">{flowInfo.description}</p>
376-
<p class="text-muted-foreground text-sm">{flowInfo.whatItDoes}</p>
414+
<p class="text-sm text-foreground leading-relaxed mb-2">{flowInfo.description}</p>
415+
<p class="text-xs text-muted-foreground">{flowInfo.whatItDoes}</p>
377416
</div>
378417

418+
<!-- How orchestration works (collapsible) -->
419+
<details class="flow-orchestration-explainer">
420+
<summary
421+
class="font-semibold text-sm text-primary cursor-pointer hover:text-primary/80 flex items-center gap-2 mb-2"
422+
>
423+
<span>⚙️</span> How SQL Core orchestrates this flow
424+
</summary>
425+
<div class="text-xs text-muted-foreground leading-relaxed bg-secondary/30 rounded p-3 space-y-2">
426+
<p>
427+
When you call <code class="bg-muted px-1 rounded font-mono"
428+
>start_flow('article_flow', {'{url}'})</code
429+
>, SQL Core creates a run and initializes state rows for each step. Root steps (no
430+
dependencies) get messages pushed to the queue immediately.
431+
</p>
432+
<p>
433+
As Workers execute handlers and call <code class="bg-muted px-1 rounded font-mono"
434+
>complete_task()</code
435+
>, SQL Core acknowledges completion, saves outputs, checks dependent steps, and starts
436+
those with all dependencies satisfied. The run completes when
437+
<code class="bg-muted px-1 rounded font-mono">remaining_steps = 0</code>.
438+
</p>
439+
<p class="text-muted-foreground/80 italic">
440+
This demo uses Supabase Realtime to broadcast graph state changes from SQL Core back to
441+
the browser in real-time.
442+
</p>
443+
</div>
444+
</details>
445+
379446
<!-- Reliability Features -->
380447
<div>
381448
<div class="font-semibold text-muted-foreground mb-1.5 text-sm flex items-center gap-2">
@@ -399,8 +466,7 @@
399466
{@html highlightedInputType}
400467
</div>
401468
<p class="text-muted-foreground text-xs mt-1.5">
402-
Start this flow with a URL object. The flow will fetch the article, process it, and
403-
publish the results.
469+
Start this flow with a URL object. SQL Core passes this input to root steps.
404470
</p>
405471
</div>
406472

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
<script lang="ts">
2+
/**
3+
* Minimal DAG visualization for showing flow context
4+
* Based on SVGDAGAnimation.astro approach
5+
* Non-interactive, just shows structure and highlights current step
6+
*/
7+
interface Props {
8+
selectedStep: string | null;
9+
}
10+
11+
let { selectedStep }: Props = $props();
12+
13+
// Node dimensions (smaller for mini view)
14+
const nodeWidth = 60;
15+
const nodeHeight = 20;
16+
17+
// Define nodes positions (x,y = center of node) - vertical layout
18+
const nodes = [
19+
{ id: 'fetch_article', x: 60, y: 20, label: 'fetch' },
20+
{ id: 'summarize', x: 25, y: 60, label: 'summ' },
21+
{ id: 'extract_keywords', x: 95, y: 60, label: 'kwrds' },
22+
{ id: 'publish', x: 60, y: 100, label: 'pub' }
23+
];
24+
25+
// Define edges
26+
const edges = [
27+
{ from: 'fetch_article', to: 'summarize' },
28+
{ from: 'fetch_article', to: 'extract_keywords' },
29+
{ from: 'summarize', to: 'publish' },
30+
{ from: 'extract_keywords', to: 'publish' }
31+
];
32+
33+
// Helper function to create smooth curved paths for vertical layout
34+
function createVerticalPath(x1: number, y1: number, x2: number, y2: number): string {
35+
const path = [];
36+
path.push(`M ${x1} ${y1}`); // Start
37+
38+
// Calculate control points for smooth bezier curve
39+
const midY = (y1 + y2) / 2;
40+
41+
// Control point 1: below start point
42+
const cp1x = x1;
43+
const cp1y = midY;
44+
45+
// Control point 2: above end point
46+
const cp2x = x2;
47+
const cp2y = midY;
48+
49+
// Cubic bezier curve
50+
path.push(`C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`);
51+
52+
return path.join(' ');
53+
}
54+
55+
// Generate edge paths
56+
const edgePaths = edges.map((edge) => {
57+
const fromNode = nodes.find((n) => n.id === edge.from);
58+
const toNode = nodes.find((n) => n.id === edge.to);
59+
60+
if (!fromNode || !toNode) return null;
61+
62+
// Start from bottom center of source node
63+
const x1 = fromNode.x;
64+
const y1 = fromNode.y + nodeHeight / 2;
65+
66+
// End at top center of target node
67+
const x2 = toNode.x;
68+
const y2 = toNode.y - nodeHeight / 2;
69+
70+
return {
71+
d: createVerticalPath(x1, y1, x2, y2),
72+
id: `${edge.from}-${edge.to}`
73+
};
74+
});
75+
76+
function getNodeClass(nodeId: string): string {
77+
return nodeId === selectedStep ? 'node selected' : 'node';
78+
}
79+
</script>
80+
81+
<svg viewBox="0 0 120 120" class="mini-dag" xmlns="http://www.w3.org/2000/svg">
82+
<!-- Edges -->
83+
{#each edgePaths as edge}
84+
{#if edge}
85+
<path class="edge" d={edge.d} />
86+
{/if}
87+
{/each}
88+
89+
<!-- Nodes -->
90+
{#each nodes as node}
91+
<g class={getNodeClass(node.id)}>
92+
<rect
93+
x={node.x - nodeWidth / 2}
94+
y={node.y - nodeHeight / 2}
95+
width={nodeWidth}
96+
height={nodeHeight}
97+
rx="2"
98+
ry="2"
99+
/>
100+
<text x={node.x} y={node.y}>{node.label}</text>
101+
</g>
102+
{/each}
103+
</svg>
104+
105+
<style>
106+
.mini-dag {
107+
width: 100%;
108+
height: 100%;
109+
display: block;
110+
}
111+
112+
.edge {
113+
stroke: #607b75;
114+
stroke-width: 1.5;
115+
fill: none;
116+
opacity: 0.5;
117+
}
118+
119+
.node rect {
120+
fill: #3d524d;
121+
stroke: #607b75;
122+
stroke-width: 1.5;
123+
transition: fill 0.2s ease, stroke 0.2s ease;
124+
}
125+
126+
.node.selected rect {
127+
fill: #3b5bdb;
128+
stroke: #5b8def;
129+
stroke-width: 2;
130+
}
131+
132+
.node text {
133+
fill: white;
134+
font-size: 10px;
135+
font-weight: 600;
136+
text-anchor: middle;
137+
dominant-baseline: middle;
138+
pointer-events: none;
139+
}
140+
</style>

0 commit comments

Comments
 (0)