Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 57 additions & 9 deletions apps/demo/src/lib/components/CodePanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import type { createFlowState } from '$lib/stores/pgflow-state-improved.svelte';
import StatusBadge from '$lib/components/StatusBadge.svelte';
import PulseDot from '$lib/components/PulseDot.svelte';
import MiniDAG from '$lib/components/MiniDAG.svelte';
Comment on lines 5 to +8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appear to be unused imports in this file. Remove any imports that are not being used in the component such as 'Card', 'CardContent', 'CardHeader', 'CardTitle', 'hoveredStep' if they exist in the import list.

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

interface Props {
flowState: ReturnType<typeof createFlowState>;
Expand Down Expand Up @@ -42,6 +43,21 @@
return blocks;
});
// Helper to trim common leading whitespace from code
function trimLeadingWhitespace(code: string): string {
const lines = code.split('\n');
// Find minimum indentation (excluding empty lines)
const minIndent = lines
.filter((line) => line.trim().length > 0)
.reduce((min, line) => {
const indent = line.match(/^\s*/)?.[0].length ?? 0;
return Math.min(min, indent);
}, Infinity);
// Remove common leading whitespace
return lines.map((line) => (line.trim().length > 0 ? line.slice(minIndent) : line)).join('\n');
}
// Helper to get status for a step badge
function getStepStatus(stepSlug: string): string | null {
const status = flowState.stepStatuses[stepSlug];
Expand Down Expand Up @@ -94,8 +110,10 @@
});
// Expanded code for explanation panel (mobile-selected)
// Trim common leading whitespace for compact display
const expandedCode = section.mobileCode || section.code;
highlightedSectionsExpanded[slug] = await codeToHtml(expandedCode, {
const trimmedCode = trimLeadingWhitespace(expandedCode);
highlightedSectionsExpanded[slug] = await codeToHtml(trimmedCode, {
lang: 'typescript',
theme: 'night-owl'
});
Expand Down Expand Up @@ -202,11 +220,18 @@

<div class="code-panel-wrapper">
{#if isMobile && selectedStep}
<!-- Mobile: Show only selected section in explanation panel (expanded version) -->
<div class="code-panel mobile-selected">
{#if highlightedSectionsExpanded[selectedStep]}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html highlightedSectionsExpanded[selectedStep]}
<!-- Mobile: Show only selected section in explanation panel (expanded version) with optional mini DAG -->
<div class="mobile-code-container">
<div class="code-panel mobile-selected">
{#if highlightedSectionsExpanded[selectedStep]}
<!-- eslint-disable-next-line svelte/no-at-html-tags -->
{@html highlightedSectionsExpanded[selectedStep]}
{/if}
</div>
{#if selectedStep !== 'flow_config'}
<div class="mini-dag-container">
<MiniDAG {selectedStep} />
</div>
{/if}
</div>
{:else if isMobile}
Expand Down Expand Up @@ -280,6 +305,19 @@
position: relative;
}
.mobile-code-container {
display: flex;
gap: 12px;
align-items: center;
}
.mini-dag-container {
flex-shrink: 0;
width: 95px;
padding-right: 12px;
opacity: 0.7;
}
.code-panel {
overflow-x: auto;
border-radius: 5px;
Expand All @@ -288,21 +326,30 @@
.code-panel.mobile-selected {
/* Compact height when showing only selected step on mobile */
min-height: auto;
font-size: 13px;
font-size: 12px;
background: #0d1117;
position: relative;
flex: 1;
}
.code-panel.mobile-selected :global(pre) {
padding: 8px 0;
}
.code-panel.mobile-selected :global(.line) {
padding: 0 12px;
}
.code-panel.mobile-sections {
/* Mobile: Container for separate section blocks */
font-size: 11px;
font-size: 12px;
border-radius: 0;
}
/* Mobile: Smaller font, no border radius (touches edges) */
@media (max-width: 768px) {
.code-panel {
font-size: 11px;
font-size: 12px;
border-radius: 0;
}
}
Expand Down Expand Up @@ -360,6 +407,7 @@
background: #0d1117 !important;
border-radius: 5px;
line-height: 1.5;
font-size: 13px; /* Desktop default */
}
/* Mobile: Smaller padding */
Expand Down
76 changes: 71 additions & 5 deletions apps/demo/src/lib/components/ExplanationPanel.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,33 @@
steps: ['fetch_article', 'summarize', 'extract_keywords', 'publish']
};

// Step-level concept explanations (how pgflow works internally)
const stepConcepts: Record<string, string> = {
fetch_article:
"This is a root step with no dependencies. When start_flow() is called, SQL Core immediately " +
"pushes a message to the queue. The Worker polls, gets the message, executes the handler, " +
"and calls complete_task() with the return value. SQL Core acknowledges completion, saves the output, " +
"and checks which dependent steps now have all their dependencies satisfied.",

summarize:
"Depends on fetch_article. After fetch_article completes, SQL Core checks if this step's " +
"dependencies are met. Since fetch_article is the only dependency, this step becomes ready " +
"immediately. SQL Core pushes a message with the input payload (fetch_article's output). " +
"Worker polls, executes the handler, calls complete_task(). SQL Core acknowledges completion and saves output.",

extract_keywords:
"Also depends only on fetch_article, so it becomes ready at the same time as summarize. " +
"Both messages hit the queue simultaneously - whichever Worker polls first starts execution. " +
"This is how pgflow achieves parallel execution: SQL Core identifies ready steps and pushes messages, " +
"Workers execute independently.",

publish:
"Depends on both summarize AND extract_keywords. This step remains blocked until both " +
"dependencies complete. After the second one finishes, complete_task() acknowledges completion, " +
"saves output, checks dependencies, and finds publish is now ready. SQL Core pushes the message " +
"with both outputs as input. After publish completes, no dependent steps remain - the run is marked completed."
};

// Step metadata for explanation
const stepInfo: Record<
string,
Expand Down Expand Up @@ -299,6 +326,18 @@
</p>
</div>

<!-- Concept explainer (collapsible) -->
<details class="concept-explainer">
<summary
class="font-semibold text-sm text-primary cursor-pointer hover:text-primary/80 flex items-center gap-2 mb-2"
>
<span>📚</span> How this step works in pgflow
</summary>
<div class="text-xs text-muted-foreground leading-relaxed bg-secondary/30 rounded p-3">
{stepConcepts[currentStepInfo.name]}
</div>
</details>

<!-- Step-level view: 2-column on desktop, single column on mobile -->
<div class="grid md:grid-cols-2 grid-cols-1 gap-4">
<!-- Left Column: Dependencies -->
Expand Down Expand Up @@ -370,12 +409,40 @@
{:else}
<!-- Flow-level view -->
<div class="space-y-3">
<!-- What it does (highlighted) -->
<!-- What it does -->
<div class="bg-accent/30 rounded-lg p-3 border border-accent">
<p class="text-foreground leading-relaxed mb-2">{flowInfo.description}</p>
<p class="text-muted-foreground text-sm">{flowInfo.whatItDoes}</p>
<p class="text-sm text-foreground leading-relaxed mb-2">{flowInfo.description}</p>
<p class="text-xs text-muted-foreground">{flowInfo.whatItDoes}</p>
</div>

<!-- How orchestration works (collapsible) -->
<details class="flow-orchestration-explainer">
<summary
class="font-semibold text-sm text-primary cursor-pointer hover:text-primary/80 flex items-center gap-2 mb-2"
>
<span>⚙️</span> How SQL Core orchestrates this flow
</summary>
<div class="text-xs text-muted-foreground leading-relaxed bg-secondary/30 rounded p-3 space-y-2">
<p>
When you call <code class="bg-muted px-1 rounded font-mono"
>start_flow('article_flow', {'{url}'})</code
>, SQL Core creates a run and initializes state rows for each step. Root steps (no
dependencies) get messages pushed to the queue immediately.
</p>
<p>
As Workers execute handlers and call <code class="bg-muted px-1 rounded font-mono"
>complete_task()</code
>, SQL Core acknowledges completion, saves outputs, checks dependent steps, and starts
those with all dependencies satisfied. The run completes when
<code class="bg-muted px-1 rounded font-mono">remaining_steps = 0</code>.
</p>
<p class="text-muted-foreground/80 italic">
This demo uses Supabase Realtime to broadcast graph state changes from SQL Core back to
the browser in real-time.
</p>
</div>
</details>

<!-- Reliability Features -->
<div>
<div class="font-semibold text-muted-foreground mb-1.5 text-sm flex items-center gap-2">
Expand All @@ -399,8 +466,7 @@
{@html highlightedInputType}
</div>
<p class="text-muted-foreground text-xs mt-1.5">
Start this flow with a URL object. The flow will fetch the article, process it, and
publish the results.
Start this flow with a URL object. SQL Core passes this input to root steps.
</p>
</div>

Expand Down
140 changes: 140 additions & 0 deletions apps/demo/src/lib/components/MiniDAG.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
<script lang="ts">
/**
* Minimal DAG visualization for showing flow context
* Based on SVGDAGAnimation.astro approach
* Non-interactive, just shows structure and highlights current step
*/
interface Props {
selectedStep: string | null;
}
let { selectedStep }: Props = $props();
// Node dimensions (smaller for mini view)
const nodeWidth = 60;
const nodeHeight = 20;
// Define nodes positions (x,y = center of node) - vertical layout
const nodes = [
{ id: 'fetch_article', x: 60, y: 20, label: 'fetch' },
{ id: 'summarize', x: 25, y: 60, label: 'summ' },
{ id: 'extract_keywords', x: 95, y: 60, label: 'kwrds' },
{ id: 'publish', x: 60, y: 100, label: 'pub' }
];
// Define edges
const edges = [
{ from: 'fetch_article', to: 'summarize' },
{ from: 'fetch_article', to: 'extract_keywords' },
{ from: 'summarize', to: 'publish' },
{ from: 'extract_keywords', to: 'publish' }
];
// Helper function to create smooth curved paths for vertical layout
function createVerticalPath(x1: number, y1: number, x2: number, y2: number): string {
const path = [];
path.push(`M ${x1} ${y1}`); // Start
// Calculate control points for smooth bezier curve
const midY = (y1 + y2) / 2;
// Control point 1: below start point
const cp1x = x1;
const cp1y = midY;
// Control point 2: above end point
const cp2x = x2;
const cp2y = midY;
// Cubic bezier curve
path.push(`C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${x2} ${y2}`);
return path.join(' ');
}
// Generate edge paths
const edgePaths = edges.map((edge) => {
const fromNode = nodes.find((n) => n.id === edge.from);
const toNode = nodes.find((n) => n.id === edge.to);
if (!fromNode || !toNode) return null;
// Start from bottom center of source node
const x1 = fromNode.x;
const y1 = fromNode.y + nodeHeight / 2;
// End at top center of target node
const x2 = toNode.x;
const y2 = toNode.y - nodeHeight / 2;
return {
d: createVerticalPath(x1, y1, x2, y2),
id: `${edge.from}-${edge.to}`
};
});
function getNodeClass(nodeId: string): string {
return nodeId === selectedStep ? 'node selected' : 'node';
}
</script>

<svg viewBox="0 0 120 120" class="mini-dag" xmlns="http://www.w3.org/2000/svg">
<!-- Edges -->
{#each edgePaths as edge}
{#if edge}
<path class="edge" d={edge.d} />
{/if}
{/each}
Comment on lines +83 to +87
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This each block is missing a key attribute. Add a key using the edge id or index: {#each edgePaths as edge, i (edge?.id || i)}

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.


<!-- Nodes -->
{#each nodes as node}
<g class={getNodeClass(node.id)}>
<rect
x={node.x - nodeWidth / 2}
y={node.y - nodeHeight / 2}
width={nodeWidth}
height={nodeHeight}
rx="2"
ry="2"
/>
<text x={node.x} y={node.y}>{node.label}</text>
</g>
{/each}
Comment on lines +90 to +102
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This each block is missing a key attribute. Add a key using the node id: {#each nodes as node (node.id)}

Spotted by Graphite Agent (based on CI logs)

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

</svg>

<style>
.mini-dag {
width: 100%;
height: 100%;
display: block;
}
.edge {
stroke: #607b75;
stroke-width: 1.5;
fill: none;
opacity: 0.5;
}
.node rect {
fill: #3d524d;
stroke: #607b75;
stroke-width: 1.5;
transition: fill 0.2s ease, stroke 0.2s ease;
}
.node.selected rect {
fill: #3b5bdb;
stroke: #5b8def;
stroke-width: 2;
}
.node text {
fill: white;
font-size: 10px;
font-weight: 600;
text-anchor: middle;
dominant-baseline: middle;
pointer-events: none;
}
</style>
Loading
Loading