From f7037953ed70f17f3a9041269377cf88037dee62 Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Mon, 18 May 2026 17:41:24 +0200 Subject: [PATCH] =?UTF-8?q?feat(docs):=20route=20ingestion=20atoms=20throu?= =?UTF-8?q?gh=20full=20source=E2=86=92output=20journey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace per-edge dots with full-journey particles: each atom is born at a source, threads the entire stage chain, and lands at either the wiki or semantic layer. Particles are tinted by their source's accent so the origin is legible. Each source produces exactly 2 atoms (8 total) to guarantee every input is visibly active, while the destination and begin offsets are randomized per page load. Particles populate on client mount to avoid hydration mismatch, and are hidden under prefers-reduced-motion. --- docs-site/components/product-mechanics.tsx | 189 +++++++++++++----- .../tests/product-mechanics-content.test.mjs | 3 +- 2 files changed, 140 insertions(+), 52 deletions(-) diff --git a/docs-site/components/product-mechanics.tsx b/docs-site/components/product-mechanics.tsx index 1aaaa411..45baaa31 100644 --- a/docs-site/components/product-mechanics.tsx +++ b/docs-site/components/product-mechanics.tsx @@ -3,7 +3,7 @@ import { Background, BackgroundVariant, - BaseEdge, + type Edge, type EdgeProps, getSmoothStepPath, Handle, @@ -14,6 +14,7 @@ import { ReactFlow, } from "@xyflow/react"; import "@xyflow/react/dist/style.css"; +import { useEffect, useMemo, useState } from "react"; type SourceNodeData = { accent: string; @@ -185,7 +186,7 @@ const flowEdges = [ })), ].map((edge) => ({ ...edge, - type: "animated" as const, + type: "smoothstep" as const, style: { stroke: EDGE_STROKE, strokeWidth: 1.5 }, markerEnd: { type: MarkerType.ArrowClosed, @@ -234,7 +235,6 @@ const refsEdge = { }, }; -const edges = [...flowEdges, refsEdge]; function SourceNodeView({ data }: NodeProps) { return ( @@ -340,56 +340,84 @@ function OutputNodeView({ data }: NodeProps) { ); } -const DOT_CORE_COLOR = "#67e8f9"; -const DOT_GLOW_COLOR = "#22d3ee"; -const DOT_SPEED_PX_PER_SEC = 110; -const DOT_MIN_DURATION_SEC = 0.7; +const PARTICLES_PER_SOURCE = 2; +const PARTICLE_SPEED_PX_PER_SEC = 130; +const PARTICLE_MIN_DURATION_SEC = 4; -function AnimatedSmoothStepEdge({ - id, - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, - style, - markerEnd, -}: EdgeProps) { - const [path] = getSmoothStepPath({ - sourceX, - sourceY, - sourcePosition, - targetX, - targetY, - targetPosition, +const stageTopY = (i: number) => ROW_STAGE_START_Y + i * (STAGE_H + STAGE_GAP); +const stageBottomY = (i: number) => stageTopY(i) + STAGE_H; + +function buildParticlePath( + sourceIndex: number, + outputIndex: number, +): { d: string; length: number } { + const sourceCenterX = + SOURCES_START_X + sourceIndex * (SOURCE_W + SOURCE_GAP_X) + SOURCE_W / 2; + const sourceBottomYVal = ROW_SOURCES_Y + SOURCE_H; + const outputCenterX = + OUTPUTS_START_X + outputIndex * (OUTPUT_W + OUTPUT_GAP_X) + OUTPUT_W / 2; + + const legs: Array<[number, number, number, number]> = [ + [sourceCenterX, sourceBottomYVal, STAGE_CENTER_X, stageTopY(0)], + [STAGE_CENTER_X, stageBottomY(0), STAGE_CENTER_X, stageTopY(1)], + [STAGE_CENTER_X, stageBottomY(1), STAGE_CENTER_X, stageTopY(2)], + [STAGE_CENTER_X, stageBottomY(2), STAGE_CENTER_X, stageTopY(3)], + [STAGE_CENTER_X, stageBottomY(3), outputCenterX, ROW_OUTPUTS_Y], + ]; + + const segments = legs.map(([sx, sy, tx, ty]) => { + const [segment] = getSmoothStepPath({ + sourceX: sx, + sourceY: sy, + sourcePosition: Position.Bottom, + targetX: tx, + targetY: ty, + targetPosition: Position.Top, + }); + return segment; }); - const pathId = `mechanics-flow-${id}`; - const approxLength = - Math.abs(targetX - sourceX) + Math.abs(targetY - sourceY); - const duration = Math.max( - DOT_MIN_DURATION_SEC, - approxLength / DOT_SPEED_PX_PER_SEC, - ); - const durAttr = `${duration.toFixed(2)}s`; - const beginAttr = `-${(duration / 2).toFixed(2)}s`; + let d = segments[0]; + for (let i = 1; i < segments.length; i += 1) { + d += ` ${segments[i].replace(/^M/, "L")}`; + } + + const length = legs.reduce( + (sum, [sx, sy, tx, ty]) => sum + Math.abs(tx - sx) + Math.abs(ty - sy), + 0, + ); + + return { d, length }; +} + +type ParticleEdgeData = { + d: string; + duration: number; + beginOffset: number; + color: string; +}; + +type ParticleEdge = Edge; + +function ParticleEdgeView({ id, data }: EdgeProps) { + if (!data) return null; + const pathId = `mechanics-particle-path-${id}`; return ( <> - - - - - - - - - - - + + + + + @@ -406,10 +434,69 @@ const nodeTypes = { }; const edgeTypes = { - animated: AnimatedSmoothStepEdge, + particle: ParticleEdgeView, }; +const staticEdges = [...flowEdges, refsEdge]; + +type ParticleSpec = { + id: string; + sourceIndex: number; + outputIndex: number; +}; + +function makeRandomParticles(perSource: number): ParticleSpec[] { + const specs: ParticleSpec[] = []; + for (let sourceIndex = 0; sourceIndex < sourceData.length; sourceIndex += 1) { + for (let n = 0; n < perSource; n += 1) { + specs.push({ + id: `particle-${sourceIndex}-${n}`, + sourceIndex, + outputIndex: Math.floor(Math.random() * outputData.length), + }); + } + } + return specs; +} + +function specToEdge(spec: ParticleSpec): { + id: string; + source: string; + target: string; + type: "particle"; + data: ParticleEdgeData; +} { + const { d, length } = buildParticlePath(spec.sourceIndex, spec.outputIndex); + const duration = Math.max( + PARTICLE_MIN_DURATION_SEC, + length / PARTICLE_SPEED_PX_PER_SEC, + ); + return { + id: spec.id, + source: `source-${spec.sourceIndex}`, + target: `output-${spec.outputIndex}`, + type: "particle", + data: { + d, + duration, + beginOffset: Math.random() * duration, + color: sourceData[spec.sourceIndex].accent, + }, + }; +} + export function ProductMechanics() { + const [particles, setParticles] = useState([]); + + useEffect(() => { + setParticles(makeRandomParticles(PARTICLES_PER_SOURCE)); + }, []); + + const edges = useMemo( + () => [...staticEdges, ...particles.map(specToEdge)], + [particles], + ); + return (
{ "