fix(kilo-docs): fix diagram scroll stealing and edge routing

- Add preventScrolling={false} and panOnScroll={false} to ReactFlow
  so mouse wheel events pass through to the page instead of being
  captured by the diagram

- Set explicit sourcePosition/targetPosition on all nodes so edges
  connect from right→left for the main horizontal flow

- Use bottom→top routing for failure/rejection transitions so lines
  don't overlap nodes

- Reposition 'failed' node and adjust spacing for cleaner layout
This commit is contained in:
John Fawcett
2026-04-30 16:00:13 -05:00
parent 196a34d8bc
commit 56f4ce3669
4 changed files with 92 additions and 66 deletions
@@ -1,113 +1,94 @@
import type { Node, Edge } from "@xyflow/react"
import type { Node, Edge, Position } from "@xyflow/react"
import type { DiagramDefinition } from "./index"
const baseStyle = {
padding: "14px 22px",
borderRadius: "8px",
fontSize: "13px",
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
textAlign: "center" as const,
minWidth: "160px",
}
const nodes: Node[] = [
{
id: "polecat-write",
position: { x: 0, y: 80 },
data: { label: "Polecat writes code" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
padding: "14px 22px",
borderRadius: "8px",
fontSize: "13px",
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
...baseStyle,
background: "rgba(59,130,246,0.1)",
borderColor: "rgba(59,130,246,0.5)",
border: "1px solid rgba(59,130,246,0.5)",
color: "#3b82f6",
minWidth: "160px",
textAlign: "center" as const,
},
},
{
id: "push",
position: { x: 230, y: 80 },
position: { x: 240, y: 80 },
data: { label: "Push branch" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
padding: "14px 22px",
borderRadius: "8px",
fontSize: "13px",
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
...baseStyle,
background: "rgba(255,255,255,0.03)",
borderColor: "rgba(255,255,255,0.15)",
border: "1px solid rgba(255,255,255,0.15)",
color: "#ccc",
minWidth: "120px",
textAlign: "center" as const,
},
},
{
id: "refinery-review",
position: { x: 420, y: 80 },
position: { x: 440, y: 80 },
data: { label: "Refinery reviews" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
padding: "14px 22px",
borderRadius: "8px",
fontSize: "13px",
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
...baseStyle,
background: "rgba(251,191,36,0.1)",
borderColor: "rgba(251,191,36,0.5)",
border: "1px solid rgba(251,191,36,0.5)",
color: "#fbbf24",
minWidth: "160px",
textAlign: "center" as const,
},
},
{
id: "approve",
position: { x: 620, y: 0 },
position: { x: 680, y: 20 },
data: { label: "✓ Approve & Merge" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
padding: "14px 22px",
borderRadius: "8px",
fontSize: "13px",
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
...baseStyle,
background: "rgba(34,197,94,0.1)",
borderColor: "rgba(34,197,94,0.5)",
border: "1px solid rgba(34,197,94,0.5)",
color: "#22c55e",
minWidth: "160px",
textAlign: "center" as const,
},
},
{
id: "feedback",
position: { x: 620, y: 160 },
position: { x: 680, y: 140 },
data: { label: "✗ Send feedback" },
sourcePosition: "bottom" as Position,
targetPosition: "left" as Position,
style: {
padding: "14px 22px",
borderRadius: "8px",
fontSize: "13px",
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
...baseStyle,
background: "rgba(249,115,22,0.1)",
borderColor: "rgba(249,115,22,0.5)",
border: "1px solid rgba(249,115,22,0.5)",
color: "#f97316",
minWidth: "160px",
textAlign: "center" as const,
},
},
{
id: "revise",
position: { x: 230, y: 220 },
position: { x: 240, y: 220 },
data: { label: "Polecat revises" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
padding: "14px 22px",
borderRadius: "8px",
fontSize: "13px",
fontFamily: "'JetBrains Mono', monospace",
fontWeight: 600,
...baseStyle,
background: "rgba(59,130,246,0.1)",
borderColor: "rgba(59,130,246,0.5)",
border: "1px solid rgba(59,130,246,0.5)",
color: "#3b82f6",
minWidth: "160px",
textAlign: "center" as const,
},
},
]
@@ -131,6 +112,7 @@ const edges: Edge[] = [
id: "review-approve",
source: "refinery-review",
target: "approve",
type: "smoothstep",
animated: true,
style: { strokeWidth: 2, stroke: "#22c55e" },
},
@@ -138,12 +120,14 @@ const edges: Edge[] = [
id: "review-feedback",
source: "refinery-review",
target: "feedback",
type: "smoothstep",
style: { strokeWidth: 2, stroke: "#f97316", strokeDasharray: "5 3" },
},
{
id: "feedback-revise",
source: "feedback",
target: "revise",
type: "smoothstep",
style: { strokeWidth: 2, stroke: "#f97316", strokeDasharray: "5 3" },
},
{
@@ -151,6 +135,8 @@ const edges: Edge[] = [
source: "revise",
target: "refinery-review",
type: "smoothstep",
sourcePosition: "right" as Position,
targetPosition: "bottom" as Position,
animated: true,
label: "re-submit",
labelStyle: { fontSize: 10, fontFamily: "'JetBrains Mono', monospace", fill: "#888" },
@@ -1,4 +1,4 @@
import type { Node, Edge } from "@xyflow/react"
import type { Node, Edge, Position } from "@xyflow/react"
import type { DiagramDefinition } from "./index"
const nodeBase = {
@@ -19,6 +19,8 @@ const nodes: Node[] = [
id: "open",
position: { x: 0, y: 100 },
data: { label: "open" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
...nodeBase.style,
background: "rgba(34,197,94,0.1)",
@@ -30,6 +32,8 @@ const nodes: Node[] = [
id: "in_progress",
position: { x: 200, y: 100 },
data: { label: "in_progress" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
...nodeBase.style,
background: "rgba(59,130,246,0.1)",
@@ -41,6 +45,8 @@ const nodes: Node[] = [
id: "in_review",
position: { x: 420, y: 100 },
data: { label: "in_review" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
...nodeBase.style,
background: "rgba(251,191,36,0.1)",
@@ -52,6 +58,8 @@ const nodes: Node[] = [
id: "closed",
position: { x: 640, y: 100 },
data: { label: "closed" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
...nodeBase.style,
background: "rgba(139,92,246,0.1)",
@@ -61,8 +69,10 @@ const nodes: Node[] = [
},
{
id: "failed",
position: { x: 200, y: 230 },
position: { x: 310, y: 240 },
data: { label: "failed" },
sourcePosition: "right" as Position,
targetPosition: "top" as Position,
style: {
...nodeBase.style,
background: "rgba(239,68,68,0.1)",
@@ -109,17 +119,20 @@ const edges: Edge[] = [
id: "review-open",
source: "in_review",
target: "open",
sourceHandle: "bottom",
targetHandle: "bottom",
type: "smoothstep",
sourcePosition: "bottom" as Position,
targetPosition: "bottom" as Position,
label: "review rejected",
labelStyle: { fontSize: 10, fontFamily: "'JetBrains Mono', monospace", fill: "#888" },
type: "smoothstep",
style: { strokeWidth: 2, stroke: "#f97316", strokeDasharray: "5 3" },
},
{
id: "progress-failed",
source: "in_progress",
target: "failed",
type: "smoothstep",
sourcePosition: "bottom" as Position,
targetPosition: "top" as Position,
label: "max retries",
labelStyle: { fontSize: 10, fontFamily: "'JetBrains Mono', monospace", fill: "#888" },
style: { strokeWidth: 2, stroke: "#ef4444", strokeDasharray: "5 3" },
@@ -1,4 +1,4 @@
import type { Node, Edge } from "@xyflow/react"
import type { Node, Edge, Position } from "@xyflow/react"
import type { DiagramDefinition } from "./index"
const stageStyle = (color: string) => ({
@@ -24,48 +24,64 @@ const nodes: Node[] = [
id: "bead1",
position: { x: 0, y: 0 },
data: { label: "1. Explore codebase" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: { ...stageStyle(closedColor), color: "#8b5cf6" },
},
{
id: "review1",
position: { x: 0, y: 80 },
position: { x: 0, y: 70 },
data: { label: "✓ reviewed" },
sourcePosition: "right" as Position,
targetPosition: "top" as Position,
style: { ...stageStyle(mergeColor), color: "#22c55e", minWidth: "140px", fontSize: "11px" },
},
{
id: "bead2",
position: { x: 200, y: 0 },
data: { label: "2. Design schema" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: { ...stageStyle(closedColor), color: "#8b5cf6" },
},
{
id: "review2",
position: { x: 200, y: 80 },
position: { x: 200, y: 70 },
data: { label: "✓ reviewed" },
sourcePosition: "right" as Position,
targetPosition: "top" as Position,
style: { ...stageStyle(mergeColor), color: "#22c55e", minWidth: "140px", fontSize: "11px" },
},
{
id: "bead3",
position: { x: 400, y: 0 },
data: { label: "3. Implement API" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: { ...stageStyle(activeColor), color: "#3b82f6" },
},
{
id: "review3",
position: { x: 400, y: 80 },
position: { x: 400, y: 70 },
data: { label: "⟳ in review" },
sourcePosition: "right" as Position,
targetPosition: "top" as Position,
style: { ...stageStyle(reviewColor), color: "#fbbf24", minWidth: "140px", fontSize: "11px" },
},
{
id: "bead4",
position: { x: 600, y: 0 },
data: { label: "4. Write tests" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: { ...stageStyle(pendingColor), color: "#666", opacity: 0.6 },
},
{
id: "landing",
position: { x: 800, y: 40 },
position: { x: 800, y: 35 },
data: { label: "Landing Review" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
...stageStyle(reviewColor),
color: "#fbbf24",
@@ -76,8 +92,10 @@ const nodes: Node[] = [
},
{
id: "main",
position: { x: 1000, y: 40 },
position: { x: 990, y: 35 },
data: { label: "→ main" },
sourcePosition: "right" as Position,
targetPosition: "left" as Position,
style: {
...stageStyle(mergeColor),
color: "#22c55e",
@@ -93,13 +111,15 @@ const edges: Edge[] = [
id: "b1-r1",
source: "bead1",
target: "review1",
type: "smoothstep",
sourcePosition: "bottom" as Position,
targetPosition: "top" as Position,
style: { strokeWidth: 2, stroke: "#8b5cf6" },
},
{
id: "r1-b2",
source: "review1",
target: "bead2",
type: "smoothstep",
animated: true,
style: { strokeWidth: 2, stroke: "#22c55e" },
label: "builds on",
@@ -109,13 +129,15 @@ const edges: Edge[] = [
id: "b2-r2",
source: "bead2",
target: "review2",
type: "smoothstep",
sourcePosition: "bottom" as Position,
targetPosition: "top" as Position,
style: { strokeWidth: 2, stroke: "#8b5cf6" },
},
{
id: "r2-b3",
source: "review2",
target: "bead3",
type: "smoothstep",
animated: true,
style: { strokeWidth: 2, stroke: "#22c55e" },
label: "builds on",
@@ -125,6 +147,9 @@ const edges: Edge[] = [
id: "b3-r3",
source: "bead3",
target: "review3",
type: "smoothstep",
sourcePosition: "bottom" as Position,
targetPosition: "top" as Position,
animated: true,
style: { strokeWidth: 2, stroke: "#3b82f6" },
},
@@ -87,9 +87,11 @@ export function FlowDiagram({ name, height = "400px" }: { name: string; height?:
nodesConnectable={false}
elementsSelectable={false}
panOnDrag={false}
panOnScroll={false}
zoomOnScroll={false}
zoomOnPinch={false}
zoomOnDoubleClick={false}
preventScrolling={false}
proOptions={{ hideAttribution: true }}
style={{ background: "#08080c" }}
>