From 7d156d9a0648b3c68df87a7d47f0986e685d3b2b Mon Sep 17 00:00:00 2001 From: Andrey Avtomonov Date: Mon, 18 May 2026 17:41:37 +0200 Subject: [PATCH] feat(docs): visualize KTX ingestion with ReactFlow diagram (#133) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(docs): visualize KTX ingestion with ReactFlow diagram Reframe the introduction around the two user-facing ingestion outputs (wiki and executable semantic layer) and replace the static product-mechanics card flow with a ReactFlow diagram: sources fan into a sequential ingest pipeline, which forks into wiki and semantic-layer outputs connected by a bidirectional "references" edge. Drop the .ktx/raw-sources internal-implementation rows from the intro table and update the content test to guard the new copy. * Improve KTX docs introduction * feat(docs): animate ingestion flow with running dots Replace static smoothstep edges in the introduction page's ingestion diagram with a custom animated edge that runs glowing cyan dots along each path, conveying the source → stage → output flow. Dot duration scales with path length and is hidden under prefers-reduced-motion. * feat(docs): route ingestion atoms through full source→output journey 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 | 999 ++++++++++-------- .../docs/getting-started/introduction.mdx | 65 +- docs-site/package.json | 7 +- .../tests/product-mechanics-content.test.mjs | 95 +- pnpm-lock.yaml | 184 ++++ 5 files changed, 870 insertions(+), 480 deletions(-) diff --git a/docs-site/components/product-mechanics.tsx b/docs-site/components/product-mechanics.tsx index 1c10e9aa..45baaa31 100644 --- a/docs-site/components/product-mechanics.tsx +++ b/docs-site/components/product-mechanics.tsx @@ -1,115 +1,502 @@ -import type { ReactNode } from "react"; +"use client"; -type SourceInput = { - name: string; - sources: string[]; - detail: string; - signal: string; +import { + Background, + BackgroundVariant, + type Edge, + type EdgeProps, + getSmoothStepPath, + Handle, + MarkerType, + type Node, + type NodeProps, + Position, + ReactFlow, +} from "@xyflow/react"; +import "@xyflow/react/dist/style.css"; +import { useEffect, useMemo, useState } from "react"; + +type SourceNodeData = { accent: string; + body: string; + items: string[]; + title: string; }; -const sourceInputs = [ - { - name: "Database structure", - sources: [ - "Postgres", - "Snowflake", - "BigQuery", - "and many others", - ], - detail: "tables, columns, types, constraints, row counts", - signal: "grounds definitions in live database structure", - accent: "border-fd-primary", - }, - { - name: "BI and usage evidence", - sources: ["Metabase", "Looker"], - detail: "historic SQL, questions, dashboards, usage patterns", - signal: "extracts joins, filters, grain, and trusted examples", - accent: "border-orange-500", - }, - { - name: "Semantic modeling", - sources: ["dbt", "MetricFlow", "LookML"], - detail: "models, metrics, dimensions, explores, joins", - signal: "maps existing modeling logic into semantic entities", - accent: "border-amber-500", - }, - { - name: "Company documentation", - sources: ["Notion"], - detail: "Notion pages, policies, caveats", - signal: "links business language back to semantic references", - accent: "border-slate-500 dark:border-cyan-200", - }, -] satisfies SourceInput[]; +type StageNodeData = { + body: string; + index: number; + title: string; +}; -const ingestSteps = [ +type OutputNodeData = { + accent: string; + body: string; + path: string; + tags: string[]; + title: string; +}; + +type SourceNode = Node; +type StageNode = Node; +type OutputNode = Node; +type FlowNode = SourceNode | StageNode | OutputNode; + +const SOURCE_W = 210; +const SOURCE_H = 200; +const STAGE_W = 280; +const STAGE_H = 120; +const OUTPUT_W = 340; +const OUTPUT_H = 232; + +const ROW_SOURCES_Y = 80; +const ROW_STAGE_START_Y = 360; +const STAGE_GAP = 30; +const ROW_OUTPUTS_Y = 1000; + +const STAGE_CENTER_X = 460; +const STAGE_X = STAGE_CENTER_X - STAGE_W / 2; + +const SOURCE_GAP_X = 24; +const SOURCES_TOTAL = SOURCE_W * 4 + SOURCE_GAP_X * 3; +const SOURCES_START_X = STAGE_CENTER_X - SOURCES_TOTAL / 2; + +const OUTPUT_GAP_X = 180; +const OUTPUTS_TOTAL = OUTPUT_W * 2 + OUTPUT_GAP_X; +const OUTPUTS_START_X = STAGE_CENTER_X - OUTPUTS_TOTAL / 2; + +const EDGE_STROKE = "#94a3b8"; + +const sourceData: SourceNodeData[] = [ { - title: "extract evidence", - body: "Pull structured facts from schemas, SQL, BI metadata, and docs.", + title: "Databases", + body: "Schemas, columns, keys, row counts, and query history.", + items: ["PostgreSQL", "Snowflake", "BigQuery", "SQLite"], + accent: "#3b82f6", }, { - title: "reconcile entities", - body: "Merge names, measures, joins, and caveats into one project model.", + title: "BI tools", + body: "Dashboards, questions, explores, usage, and trusted examples.", + items: ["Metabase", "Looker"], + accent: "#f97316", }, { - title: "validate references", - body: "Check semantic fields and joins against database context before agents use them.", + title: "Modeling code", + body: "Existing metrics, dimensions, models, joins, and entities.", + items: ["dbt", "LookML", "MetricFlow"], + accent: "#f59e0b", + }, + { + title: "Docs and notes", + body: "Policies, caveats, team definitions, and analyst context.", + items: ["Notion", "Any text"], + accent: "#10b981", }, ]; -const artifacts = [ +const stageData: Omit[] = [ { - path: "semantic-layer/*.yaml", - title: "Typed query model", - body: "sources, grain, joins, dimensions, measures, filters, segments", + title: "Source adapters", + body: "Read each configured system in its native shape.", }, { + title: "Context builder", + body: "Turn source evidence into proposed context updates.", + }, + { + title: "Reconciliation", + body: "Merge new evidence with the context that already exists.", + }, + { + title: "Validation", + body: "Check references and semantics before agents rely on them.", + }, +]; + +const outputData: OutputNodeData[] = [ + { + title: "Wiki", path: "wiki/*.md", - title: "Business context", - body: "rules and caveats with sl_refs back to semantic-layer entities", + tags: ["free-form", "auto-maintained"], + body: "Definitions, caveats, policies, analyst notes, and business language that agents can search.", + accent: "#10b981", }, { - path: "raw-sources/", - title: "Evidence trail", - body: "scan artifacts, extracted metadata, relationship evidence", - }, - { - path: ".ktx/", - title: "Local indexes", - body: "embeddings and search indexes, not the source of truth", + title: "Semantic layer", + path: "semantic-layer/*.yaml", + tags: ["structured", "executable", "auto-maintained"], + body: "Metrics, joins, tables, dimensions, filters, and segments that KTX can validate and compile into SQL.", + accent: "#3b82f6", }, ]; -const runtimeSteps = [ - { - title: "Search wiki", - body: "Find business rules, caveats, synonyms, and sl_refs.", - }, - { - title: "Resolve semantic refs", - body: "Map measure and dimension names to approved entities.", - }, - { - title: "Validate fields", - body: "Check source, columns, joins, grain, filters, and segments.", - }, - { - title: "Build query plan", - body: "Create a semantic query plan before SQL is generated.", - }, - { - title: "Compile dialect SQL", - body: "Generate warehouse-shaped SQL instead of copying examples.", - }, - { - title: "Execute with bounds", - body: "Optionally run with bounded rows and return provenance.", - }, +const nodes: FlowNode[] = [ + ...sourceData.map((source, index) => ({ + id: `source-${index}`, + type: "source", + position: { + x: SOURCES_START_X + index * (SOURCE_W + SOURCE_GAP_X), + y: ROW_SOURCES_Y, + }, + data: source, + draggable: false, + selectable: false, + })), + ...stageData.map((stage, index) => ({ + id: `stage-${index}`, + type: "stage", + position: { + x: STAGE_X, + y: ROW_STAGE_START_Y + index * (STAGE_H + STAGE_GAP), + }, + data: { ...stage, index: index + 1 }, + draggable: false, + selectable: false, + })), + ...outputData.map((output, index) => ({ + id: `output-${index}`, + type: "output", + position: { + x: OUTPUTS_START_X + index * (OUTPUT_W + OUTPUT_GAP_X), + y: ROW_OUTPUTS_Y, + }, + data: output, + draggable: false, + selectable: false, + })), ]; +const REF_EDGE_STROKE = "#64748b"; + +const flowEdges = [ + ...sourceData.map((_, index) => ({ + id: `e-source-${index}-stage-0`, + source: `source-${index}`, + target: "stage-0", + })), + ...stageData.slice(0, -1).map((_, index) => ({ + id: `e-stage-${index}-stage-${index + 1}`, + source: `stage-${index}`, + target: `stage-${index + 1}`, + })), + ...outputData.map((_, index) => ({ + id: `e-stage-3-output-${index}`, + source: "stage-3", + target: `output-${index}`, + })), +].map((edge) => ({ + ...edge, + type: "smoothstep" as const, + style: { stroke: EDGE_STROKE, strokeWidth: 1.5 }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: EDGE_STROKE, + width: 16, + height: 16, + }, +})); + +const refsEdge = { + id: "e-output-refs", + source: "output-0", + sourceHandle: "right", + target: "output-1", + targetHandle: "left", + type: "straight" as const, + label: "references", + labelBgPadding: [6, 3] as [number, number], + labelBgBorderRadius: 4, + labelStyle: { + fontSize: 13, + fontWeight: 500, + fill: "var(--color-fd-muted-foreground)", + }, + labelBgStyle: { + fill: "var(--color-fd-background)", + stroke: "var(--color-fd-border)", + strokeWidth: 1, + }, + style: { + stroke: REF_EDGE_STROKE, + strokeWidth: 1.25, + strokeDasharray: "4 4", + }, + markerStart: { + type: MarkerType.ArrowClosed, + color: REF_EDGE_STROKE, + width: 14, + height: 14, + }, + markerEnd: { + type: MarkerType.ArrowClosed, + color: REF_EDGE_STROKE, + width: 14, + height: 14, + }, +}; + + +function SourceNodeView({ data }: NodeProps) { + return ( +
+ +

+ {data.title} +

+

+ {data.body} +

+
+ {data.items.map((item) => ( + + {item} + + ))} +
+ +
+ ); +} + +function StageNodeView({ data }: NodeProps) { + return ( +
+ + + {data.index} + +
+

+ {data.title} +

+

+ {data.body} +

+
+ +
+ ); +} + +function OutputNodeView({ data }: NodeProps) { + return ( +
+ + + +

+ {data.path} +

+

+ {data.title} +

+
+ {data.tags.map((tag) => ( + + {tag} + + ))} +
+

+ {data.body} +

+
+ ); +} + +const PARTICLES_PER_SOURCE = 2; +const PARTICLE_SPEED_PX_PER_SEC = 130; +const PARTICLE_MIN_DURATION_SEC = 4; + +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; + }); + + 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 ( + <> + + + + + + + + + + + ); +} + +const nodeTypes = { + source: SourceNodeView, + stage: StageNodeView, + output: OutputNodeView, +}; + +const edgeTypes = { + 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 (
- How KTX works + How ingestion works

- KTX reads source evidence, writes local context files, and gives - agents semantic search, validation, SQL, and provenance. + KTX ingests source evidence, reconciles it with your existing project, + and produces durable context that agents can search, review, and + execute.

-
- - -
+
+
+

+ Ingestion flow +

+

+ From scattered source systems to agent-ready context +

+

+ The inputs can be structured systems or loose team knowledge. The + outputs are the two files agents need: a readable wiki and an + executable semantic layer. +

+
+ +
+ + + +
+
+
); } - -function IngestionDiagram() { - return ( -
- - -
-
- Inputs KTX reads -
- {sourceInputs.map((source) => ( -
-
-

- {source.name} -

-

- {source.detail} -

-

- {source.signal} -

-
- -
- ))} -
-
- -
- KTX transforms evidence -
-
-

- KTX builds the model -

-
    - {ingestSteps.map((step, index) => ( - - ))} -
-
- -
-

- Outputs KTX writes -

-
- {artifacts.map((artifact) => ( - - ))} -
-
-
-
-
-
- ); -} - -function RuntimeDiagram() { - return ( -
- - -
-
- Agent sends - -
connection: warehouse
-
measure: orders.total_revenue
-
dimension: customers.segment
-
filter: orders.created_date >= '2024-01-01'
-
-

- This is the API surface agents should use: compact semantic intent, - not hand-written warehouse SQL. -

-
- -
- KTX planning and execution -
    - {runtimeSteps.map((step, index) => ( - - ))} -
-
-
- -
-
- Semantic query plan -
-

- source:{" "} - orders joined to customers as many_to_one -

-

- measure:{" "} - total_revenue = sum(amount) with refund filter -

-

- grain: segment - group-by with date predicate -

-

- result: dialect - SQL, bounded rows, and provenance -

-
-
- -
- KTX returns - -
select
-
customers.segment,
-
sum(orders.amount) as total_revenue
-
from analytics.orders
-
join analytics.customers
-
on orders.customer_id = customers.id
-
where orders.status != 'refunded'
-
and orders.created_date >= '2024-01-01'
-
group by 1
-
-

- The output can be SQL-only or executed results with provenance, so - the agent can show where the answer came from. -

-
-
-
- ); -} - -function DiagramHeader({ - body, - eyebrow, - id, - title, -}: { - body: string; - eyebrow: string; - id: string; - title: string; -}) { - return ( -
-

- {eyebrow} -

-

- {title} -

-

- {body} -

-
- ); -} - -function Artifact({ - body, - path, - title, -}: { - body: string; - path: string; - title: string; -}) { - return ( -
-

- {path} -

-

{title}

-

- {body} -

-
- ); -} - -function PipelineStep({ - body, - dark = false, - index, - title, -}: { - body: string; - dark?: boolean; - index: number; - title: string; -}) { - return ( -
  • - - {index} - - - - {title} - - - {body} - - -
  • - ); -} - -function ColumnLabel({ children }: { children: ReactNode }) { - return ( -

    - {children} -

    - ); -} - -function SourceList({ - sources, -}: { - sources: string[]; -}) { - return ( -
    -

    - Sources -

    -
    - {sources.map((source) => - source === "and many others" ? ( - - {source} - - ) : ( - - {source} - - ), - )} -
    -
    - ); -} - -function CodeBox({ children }: { children: ReactNode }) { - return ( -
    -
    {children}
    -
    - ); -} diff --git a/docs-site/content/docs/getting-started/introduction.mdx b/docs-site/content/docs/getting-started/introduction.mdx index 499d25a0..d0ee126d 100644 --- a/docs-site/content/docs/getting-started/introduction.mdx +++ b/docs-site/content/docs/getting-started/introduction.mdx @@ -1,6 +1,6 @@ --- title: Introduction -description: What KTX is, how it works, and where to start. +description: KTX is an open-source, self-improving context layer for data agents. --- import { ProductMechanics } from "@/components/product-mechanics"; @@ -23,46 +23,67 @@ import { ProductMechanics } from "@/components/product-mechanics"; Make analytics context usable by agents

    - {'KTX turns warehouse metadata, semantic definitions, BI usage, and team knowledge into local files and runtime tools that database agents can trust.'} + {'KTX is an open-source context layer for database agents. It turns warehouse metadata, BI models, query history, docs, and approved metric definitions into reviewable files agents can search and execute.'}

    -## Why KTX +## Why KTX helps -- Schemas show columns, not business rules. -- Agents need trusted metrics, joins, filters, caveats, and provenance. -- KTX captures that context before agents write SQL, docs, or semantic edits. +KTX gives agents a shared context workspace before they write SQL, answer a +question, or update analytics definitions. -## What KTX creates +- **Context as code.** KTX writes wiki pages and semantic-layer definitions as + git-based files you can review, diff, and merge. +- **Self-improving ingest.** KTX reads warehouses, BI tools, modeling code, + query history, and notes, then reconciles new evidence with accepted context. +- **Executable semantics.** Agents can use approved measures, joins, filters, + dimensions, and segments instead of rebuilding canonical SQL from scratch. +- **Agent-native access.** CLI and MCP tools let agents search context, compile + semantic queries, run read-only SQL, and propose updates. -| Path | What it gives agents | -|------|----------------------| -| `semantic-layer/` | Measures, dimensions, joins, grain, filters, segments | -| `wiki/` | Business definitions, caveats, policies, analyst notes | -| `raw-sources/` | Extracted metadata, scan output, relationship evidence | -| `.ktx/` | Local indexes, embeddings, setup state, runtime data | +KTX complements existing semantic layers by pairing metric definitions with the +surrounding business knowledge, caveats, provenance, and review workflow agents +need for data work. + +## How KTX works + +KTX has two connected sides: it builds and maintains the context layer, then +serves that context to agents at runtime. + +| Side | What KTX does | +|------|---------------| +| **Ingest and auto-maintain knowledge** | Reads your data stack and company knowledge, reconciles new evidence with accepted context, and keeps changes to `semantic-layer/` plus `wiki/` as version-controlled diffs automatically. | +| **Serve agents at runtime** | Helps agents find the right wiki pages and semantic-layer entities, then compile or execute semantic queries through CLI and MCP tools. | ## Use it for -- **Generate SQL** from approved measures, dimensions, joins, and filters -- **Explain provenance** with wiki context and warehouse evidence -- **Repair context** through reviewable YAML and Markdown diffs -- **Work alongside** dbt, LookML, MetricFlow, Looker, Metabase, and warehouses +Use KTX when agents need more than raw database access. Agents can search wiki +context, find semantic-layer entities, compile trusted semantic queries, run +read-only SQL, and use the same tools through MCP. -Databases: SQLite, PostgreSQL, Snowflake, BigQuery, ClickHouse, MySQL, SQL -Server. +- Generate SQL from approved metrics, joins, filters, and dimensions. +- Explain metric provenance with wiki context and source evidence. +- Repair context through reviewable YAML and Markdown diffs. +- Work alongside dbt, MetricFlow, LookML, Looker, Metabase, Notion, and + supported databases. ## Start here +Choose the route that matches what you want to do next. The quickstart is the +best first step for users; contributor setup lives in the community docs. + - Set up KTX and build your first context in under 10 minutes. + Install KTX, run setup, build context, and connect an agent. - - Hands-on workflows for scanning, ingesting, writing, and serving. + + Understand why agents need more than schema access and raw SQL. + + + Refresh context from databases, BI tools, query history, and documents. Edit semantic-layer YAML and wiki Markdown safely. diff --git a/docs-site/package.json b/docs-site/package.json index 229af1e3..4cf896ff 100644 --- a/docs-site/package.json +++ b/docs-site/package.json @@ -10,6 +10,7 @@ "test": "node --test tests/*.test.mjs" }, "dependencies": { + "@xyflow/react": "^12.10.2", "fumadocs-core": "16.8.10", "fumadocs-mdx": "15.0.4", "fumadocs-ui": "16.8.10", @@ -18,11 +19,11 @@ "react-dom": "19.2.6" }, "devDependencies": { + "@tailwindcss/postcss": "^4", "@types/node": "^25.7.0", "@types/react": "^19", "@types/react-dom": "^19", - "typescript": "^6.0", - "@tailwindcss/postcss": "^4", - "tailwindcss": "^4" + "tailwindcss": "^4", + "typescript": "^6.0" } } diff --git a/docs-site/tests/product-mechanics-content.test.mjs b/docs-site/tests/product-mechanics-content.test.mjs index bf03abcc..81d716d3 100644 --- a/docs-site/tests/product-mechanics-content.test.mjs +++ b/docs-site/tests/product-mechanics-content.test.mjs @@ -23,7 +23,7 @@ test("docs introduction frames the concept before showing product mechanics", as const heroIndex = introduction.indexOf("Make analytics context"); const whyIndex = introduction.indexOf("## Why KTX"); - const createsIndex = introduction.indexOf("## What KTX creates"); + const worksIndex = introduction.indexOf("## How KTX works"); const mechanicsIndex = introduction.indexOf(""); const useCaseIndex = introduction.indexOf("## Use it for"); const heroSource = introduction.slice(0, mechanicsIndex); @@ -34,12 +34,12 @@ test("docs introduction frames the concept before showing product mechanics", as "problem framing should appear after the hero", ); assert.ok( - createsIndex > whyIndex, - "artifact summary should appear after problem framing", + worksIndex > whyIndex, + "mechanics bridge should appear after problem framing", ); assert.ok( - mechanicsIndex > createsIndex, - "mechanics component should appear after the artifact summary", + mechanicsIndex > worksIndex, + "mechanics component should appear after the mechanics bridge", ); assert.ok( mechanicsIndex < useCaseIndex, @@ -49,49 +49,47 @@ test("docs introduction frames the concept before showing product mechanics", as assert.doesNotMatch(heroSource, /The Context Layer/); assert.doesNotMatch(heroSource, /Building Context/); assert.doesNotMatch(heroSource, /flex flex-wrap gap-3/); + assert.doesNotMatch(introduction, /raw-sources/); + assert.doesNotMatch(introduction, /\.ktx/); }); -test("product mechanics component covers source-specific context and SQL expansion", async () => { +test("product mechanics component explains ingestion outputs", async () => { const component = await readDocsFile("components/product-mechanics.tsx"); for (const expectedText of [ - "How KTX works", - "Build context from source evidence", - "Run agent requests through the model", - "Ingestion", - "Runtime", - "wiki/", - "semantic-layer/", - "raw-sources/", - ".ktx/", - "sl_refs", - "Database structure", - "BI and usage evidence", - "Semantic modeling", - "Company documentation", - "Notion pages", - "Sources", - "KTX transforms evidence", - "KTX builds the model", - "Outputs KTX writes", - "Postgres", + "How ingestion works", + "Ingestion flow", + "From scattered source systems to agent-ready context", + "wiki/*.md", + "semantic-layer/*.yaml", + "Wiki", + "Semantic layer", + "Databases", + "BI tools", + "Modeling code", + "Docs and notes", + "Source adapters", + "Context builder", + "Reconciliation", + "Validation", + "PostgreSQL", "Snowflake", "BigQuery", - "and many others", "Metabase", "Looker", + "dbt", "MetricFlow", "LookML", - "extract evidence", - "reconcile entities", - "validate references", - "semantic query plan", - "dialect SQL", - "bounded rows", - "provenance", - "measure: orders.total_revenue", - "dimension: customers.segment", - "select", + "Notion", + "Any text", + "compile into SQL", + '"use client"', + "@xyflow/react", + "=17' + react-dom: '>=17' + + '@xyflow/system@0.0.76': + resolution: {integrity: sha512-hvwvnRS1B3REwVDlWexsq7YQaPZeG3/mKo1jv38UmnpWmxihp14bW6VtEOuHEwJX2FvzFw8k77LyKSk/wiZVNA==} + abort-controller@3.0.0: resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} engines: {node: '>=6.5'} @@ -3139,6 +3169,9 @@ packages: class-variance-authority@0.7.1: resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + classcat@5.0.5: + resolution: {integrity: sha512-JhZUT7JFcQy/EzW605k/ktHtncoo9vnyW/2GspNYwFlN1C/WmjuV/xtS04e9SOkL2sTdw0VAZ2UGCcQ9lR6p6w==} + clean-stack@2.2.0: resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} engines: {node: '>=6'} @@ -3325,6 +3358,44 @@ packages: csstype@3.2.3: resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + d3-color@3.1.0: + resolution: {integrity: sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==} + engines: {node: '>=12'} + + d3-dispatch@3.0.1: + resolution: {integrity: sha512-rzUyPU/S7rwUflMyLc1ETDeBj0NRuHKKAcvukozwhshr6g6c5d8zh4c2gQjY2bZ0dXeGLWc1PF174P2tVvKhfg==} + engines: {node: '>=12'} + + d3-drag@3.0.0: + resolution: {integrity: sha512-pWbUJLdETVA8lQNJecMxoXfH6x+mO2UQo8rSmZ+QqxcbyA3hfeprFgIT//HW2nlHChWeIIMwS2Fq+gEARkhTkg==} + engines: {node: '>=12'} + + d3-ease@3.0.1: + resolution: {integrity: sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==} + engines: {node: '>=12'} + + d3-interpolate@3.0.1: + resolution: {integrity: sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==} + engines: {node: '>=12'} + + d3-selection@3.0.0: + resolution: {integrity: sha512-fmTRWbNMmsmWq6xJV8D19U/gw/bwrHfNXxrIN+HfZgnzqTHp9jOmKMhsTUjXOJnZOdZY9Q28y4yebKzqDKlxlQ==} + engines: {node: '>=12'} + + d3-timer@3.0.1: + resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==} + engines: {node: '>=12'} + + d3-transition@3.0.1: + resolution: {integrity: sha512-ApKvfjsSR6tg06xrL434C0WydLr7JewBB3V+/39RMHsaXTOG0zmt/OAXeng5M5LBm0ojmxJrpomQVZ1aPvBL4w==} + engines: {node: '>=12'} + peerDependencies: + d3-selection: 2 - 3 + + d3-zoom@3.0.0: + resolution: {integrity: sha512-b8AmV3kfQaqWAuacbPuNbL6vahnOJflOhexLzMMNLga62+/nh0JzvJ0aO/5a5MVgUFGS7Hu1P9P03o3fJkDCyw==} + engines: {node: '>=12'} + data-uri-to-buffer@4.0.1: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} @@ -5937,6 +6008,11 @@ packages: '@types/react': optional: true + use-sync-external-store@1.6.0: + resolution: {integrity: sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==} + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} @@ -6158,6 +6234,21 @@ packages: zod@4.4.3: resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} + zustand@4.5.7: + resolution: {integrity: sha512-CHOUy7mu3lbD6o6LJLfllpjkzhHXSBlX8B9+qPddUsIfeF5S/UZ5q0kmCsnRqT1UHFQZchNFDDzMbQsuesHWlw==} + engines: {node: '>=12.7.0'} + peerDependencies: + '@types/react': '>=16.8' + immer: '>=9.0.6' + react: '>=16.8' + peerDependenciesMeta: + '@types/react': + optional: true + immer: + optional: true + react: + optional: true + zwitch@2.0.4: resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} @@ -9061,6 +9152,27 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/d3-color@3.1.3': {} + + '@types/d3-drag@3.0.7': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-interpolate@3.0.4': + dependencies: + '@types/d3-color': 3.1.3 + + '@types/d3-selection@3.0.11': {} + + '@types/d3-transition@3.0.9': + dependencies: + '@types/d3-selection': 3.0.11 + + '@types/d3-zoom@3.0.8': + dependencies: + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -9191,6 +9303,29 @@ snapshots: convert-source-map: 2.0.0 tinyrainbow: 3.1.0 + '@xyflow/react@12.10.2(@types/react@19.2.14)(react-dom@19.2.6(react@19.2.6))(react@19.2.6)': + dependencies: + '@xyflow/system': 0.0.76 + classcat: 5.0.5 + react: 19.2.6 + react-dom: 19.2.6(react@19.2.6) + zustand: 4.5.7(@types/react@19.2.14)(react@19.2.6) + transitivePeerDependencies: + - '@types/react' + - immer + + '@xyflow/system@0.0.76': + dependencies: + '@types/d3-drag': 3.0.7 + '@types/d3-interpolate': 3.0.4 + '@types/d3-selection': 3.0.11 + '@types/d3-transition': 3.0.9 + '@types/d3-zoom': 3.0.8 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-zoom: 3.0.0 + abort-controller@3.0.0: dependencies: event-target-shim: 5.0.1 @@ -9459,6 +9594,8 @@ snapshots: dependencies: clsx: 2.1.1 + classcat@5.0.5: {} + clean-stack@2.2.0: {} clean-stack@5.3.0: @@ -9631,6 +9768,42 @@ snapshots: csstype@3.2.3: {} + d3-color@3.1.0: {} + + d3-dispatch@3.0.1: {} + + d3-drag@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-selection: 3.0.0 + + d3-ease@3.0.1: {} + + d3-interpolate@3.0.1: + dependencies: + d3-color: 3.1.0 + + d3-selection@3.0.0: {} + + d3-timer@3.0.1: {} + + d3-transition@3.0.1(d3-selection@3.0.0): + dependencies: + d3-color: 3.1.0 + d3-dispatch: 3.0.1 + d3-ease: 3.0.1 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-timer: 3.0.1 + + d3-zoom@3.0.0: + dependencies: + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-interpolate: 3.0.1 + d3-selection: 3.0.0 + d3-transition: 3.0.1(d3-selection@3.0.0) + data-uri-to-buffer@4.0.1: {} debug@4.4.3: @@ -12730,6 +12903,10 @@ snapshots: optionalDependencies: '@types/react': 19.2.14 + use-sync-external-store@1.6.0(react@19.2.6): + dependencies: + react: 19.2.6 + util-deprecate@1.0.2: {} validate-npm-package-license@3.0.4: @@ -12907,4 +13084,11 @@ snapshots: zod@4.4.3: {} + zustand@4.5.7(@types/react@19.2.14)(react@19.2.6): + dependencies: + use-sync-external-store: 1.6.0(react@19.2.6) + optionalDependencies: + '@types/react': 19.2.14 + react: 19.2.6 + zwitch@2.0.4: {}