2026-05-15 15:31:51 -04:00
|
|
|
import assert from "node:assert/strict";
|
|
|
|
|
import { readFile } from "node:fs/promises";
|
|
|
|
|
import { dirname, join } from "node:path";
|
|
|
|
|
import { test } from "node:test";
|
|
|
|
|
import { fileURLToPath } from "node:url";
|
|
|
|
|
|
|
|
|
|
const docsSiteDir = join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
|
|
|
|
|
|
|
|
async function readDocsFile(path) {
|
|
|
|
|
return readFile(join(docsSiteDir, path), "utf8");
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-16 13:25:37 -04:00
|
|
|
test("docs introduction frames the concept before showing product mechanics", async () => {
|
2026-05-15 15:31:51 -04:00
|
|
|
const introduction = await readDocsFile(
|
|
|
|
|
"content/docs/getting-started/introduction.mdx",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.match(
|
|
|
|
|
introduction,
|
|
|
|
|
/import\s+\{\s*ProductMechanics\s*\}\s+from\s+"@\/components\/product-mechanics";/,
|
|
|
|
|
);
|
|
|
|
|
assert.match(introduction, /<ProductMechanics\s*\/>/);
|
|
|
|
|
|
|
|
|
|
const heroIndex = introduction.indexOf("Make analytics context");
|
2026-05-20 17:33:38 +02:00
|
|
|
const whyIndex = introduction.indexOf("## Why ktx helps");
|
|
|
|
|
const worksIndex = introduction.indexOf("## How ktx works");
|
2026-05-15 15:31:51 -04:00
|
|
|
const mechanicsIndex = introduction.indexOf("<ProductMechanics />");
|
2026-05-16 13:25:37 -04:00
|
|
|
const useCaseIndex = introduction.indexOf("## Use it for");
|
2026-05-15 15:31:51 -04:00
|
|
|
const heroSource = introduction.slice(0, mechanicsIndex);
|
|
|
|
|
|
|
|
|
|
assert.ok(heroIndex >= 0, "introduction should include the custom hero");
|
|
|
|
|
assert.ok(
|
2026-05-16 13:25:37 -04:00
|
|
|
whyIndex > heroIndex,
|
|
|
|
|
"problem framing should appear after the hero",
|
|
|
|
|
);
|
|
|
|
|
assert.ok(
|
2026-05-18 17:41:37 +02:00
|
|
|
worksIndex > whyIndex,
|
|
|
|
|
"mechanics bridge should appear after problem framing",
|
2026-05-16 13:25:37 -04:00
|
|
|
);
|
|
|
|
|
assert.ok(
|
2026-05-18 17:41:37 +02:00
|
|
|
mechanicsIndex > worksIndex,
|
|
|
|
|
"mechanics component should appear after the mechanics bridge",
|
2026-05-15 15:31:51 -04:00
|
|
|
);
|
|
|
|
|
assert.ok(
|
|
|
|
|
mechanicsIndex < useCaseIndex,
|
|
|
|
|
"mechanics component should appear before use-case sections",
|
|
|
|
|
);
|
|
|
|
|
assert.doesNotMatch(heroSource, /Get Started/);
|
|
|
|
|
assert.doesNotMatch(heroSource, /The Context Layer/);
|
|
|
|
|
assert.doesNotMatch(heroSource, /Building Context/);
|
|
|
|
|
assert.doesNotMatch(heroSource, /flex flex-wrap gap-3/);
|
2026-05-18 17:41:37 +02:00
|
|
|
assert.doesNotMatch(introduction, /raw-sources/);
|
|
|
|
|
assert.doesNotMatch(introduction, /\.ktx/);
|
2026-05-15 15:31:51 -04:00
|
|
|
});
|
|
|
|
|
|
2026-05-18 17:41:37 +02:00
|
|
|
test("product mechanics component explains ingestion outputs", async () => {
|
2026-05-15 15:31:51 -04:00
|
|
|
const component = await readDocsFile("components/product-mechanics.tsx");
|
|
|
|
|
|
|
|
|
|
for (const expectedText of [
|
2026-05-18 17:41:37 +02:00
|
|
|
"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",
|
2026-05-20 17:33:38 +02:00
|
|
|
"Source connectors",
|
2026-05-18 17:41:37 +02:00
|
|
|
"Context builder",
|
|
|
|
|
"Reconciliation",
|
|
|
|
|
"Validation",
|
|
|
|
|
"PostgreSQL",
|
2026-05-16 13:25:37 -04:00
|
|
|
"Snowflake",
|
|
|
|
|
"BigQuery",
|
2026-05-15 15:31:51 -04:00
|
|
|
"Metabase",
|
2026-05-16 13:25:37 -04:00
|
|
|
"Looker",
|
2026-05-18 17:41:37 +02:00
|
|
|
"dbt",
|
2026-05-16 13:25:37 -04:00
|
|
|
"MetricFlow",
|
|
|
|
|
"LookML",
|
2026-05-18 17:41:37 +02:00
|
|
|
"Notion",
|
|
|
|
|
"Any text",
|
|
|
|
|
"compile into SQL",
|
|
|
|
|
'"use client"',
|
|
|
|
|
"@xyflow/react",
|
|
|
|
|
"<ReactFlow",
|
|
|
|
|
"getSmoothStepPath",
|
|
|
|
|
"animateMotion",
|
|
|
|
|
"mechanics-particle",
|
|
|
|
|
"buildParticlePath",
|
2026-05-15 15:31:51 -04:00
|
|
|
]) {
|
|
|
|
|
assert.ok(
|
|
|
|
|
component.includes(expectedText),
|
|
|
|
|
`component should include: ${expectedText}`,
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
2026-05-18 17:41:37 +02:00
|
|
|
assert.match(
|
|
|
|
|
component,
|
|
|
|
|
/nodesDraggable=\{false\}/,
|
|
|
|
|
"ReactFlow canvas should disable node dragging",
|
|
|
|
|
);
|
|
|
|
|
assert.match(
|
|
|
|
|
component,
|
|
|
|
|
/panOnDrag=\{false\}/,
|
|
|
|
|
"ReactFlow canvas should disable panning",
|
|
|
|
|
);
|
|
|
|
|
assert.match(
|
|
|
|
|
component,
|
|
|
|
|
/zoomOnScroll=\{false\}/,
|
|
|
|
|
"ReactFlow canvas should disable scroll zoom",
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.doesNotMatch(component, /raw-sources/);
|
|
|
|
|
assert.doesNotMatch(component, /\.ktx/);
|
2026-05-16 13:25:37 -04:00
|
|
|
assert.doesNotMatch(component, /Product mechanics/);
|
2026-05-20 17:33:38 +02:00
|
|
|
assert.doesNotMatch(component, /How ktx works/);
|
2026-05-18 17:41:37 +02:00
|
|
|
assert.doesNotMatch(component, /Runtime/);
|
2026-05-16 13:25:37 -04:00
|
|
|
assert.doesNotMatch(component, /A semantic compiler for analytics agents/);
|
2026-05-20 17:33:38 +02:00
|
|
|
assert.doesNotMatch(component, /ktx does more than retrieve Markdown/);
|
2026-05-15 15:31:51 -04:00
|
|
|
assert.doesNotMatch(component, /Plain Markdown \+ RAG/);
|
|
|
|
|
assert.doesNotMatch(component, /comparisonRows/);
|
|
|
|
|
assert.doesNotMatch(component, /ComparisonTable/);
|
|
|
|
|
assert.doesNotMatch(component, /Not just retrieval/);
|
2026-05-20 17:33:38 +02:00
|
|
|
assert.doesNotMatch(component, /ktx works in two moments/);
|
2026-05-16 13:25:37 -04:00
|
|
|
assert.doesNotMatch(component, /name: "Metabase and query history"/);
|
|
|
|
|
assert.doesNotMatch(component, /name: "dbt, MetricFlow, LookML"/);
|
|
|
|
|
assert.doesNotMatch(component, /MySQL/);
|
|
|
|
|
assert.doesNotMatch(component, /SQL Server/);
|
|
|
|
|
assert.doesNotMatch(
|
|
|
|
|
component,
|
docs: rewrite Semantic Querying concept with imperative-vs-declarative diagram (#156)
* docs: rewrite Semantic Querying concept with imperative-vs-declarative diagram
Reframe semantic-layer-internals.mdx around the contract the semantic
layer offers an agent: declare what you want (a Semantic Query), KTX
figures out how to compute it. Replaces the old "Context-Aware SQL"
framing with a clear imperative-vs-declarative narrative.
Adds a React Flow component (semantic-layer-flow.tsx) that contrasts a
buggy 4-table agent-authored SQL (chasm trap, LEFT-JOIN-in-WHERE,
hardcoded DATE_TRUNC) against the chasm-safe per-fact CTE SQL the
planner actually emits, including the outer GROUP BY over the requested
dimensions. Both lanes converge into a shared warehouse node and each
SQL card now has parallel bullet notes (failures on the left, KTX
behavior on the right).
Side fixes bundled in:
- include the /ktx basePath in the favicon metadata so the icon resolves
under the production prefix
- migrate docs-site/middleware.ts to docs-site/proxy.ts (Next 16 rename)
- redirect / to /ktx/docs/getting-started/introduction so the apex docs
URL works
- add tests covering the apex redirect, the favicon basePath, and the
middleware-to-proxy rename
- propagate the Semantic Query terminology across the ktx-sl CLI
reference, the context-layer concept page, and the agent-clients /
primary-sources integration pages
* Fix CI dead-code failures
* docs-site: polish semantic-layer-internals code blocks and flow diagram
- Make CodeBlock a server component so children traverse synchronously
under React 19 RSC streaming; previously extractText returned "" in
dev SSR, leaving code blocks empty.
- Add custom JSON/YAML/SQL/code-like tokenizers with theme-aware token
classes; drop the colored file-glyph dot and gradient tab-head.
- Tighten tab-head: subtle grey background, smaller monospace filename
in muted grey, smaller rectangular language pill placed to the left
of the filename.
- Polish the React Flow semantic-layer diagram (controls, fit-view
padding, edge types).
* docs-site: annotate imperative SQL, add section anchor, drop ClickHouse
- Wire numbered red badges to each problematic span in the "Without KTX"
SQL with hover sync between SQL gutter, lines, and the notes list.
- Add #imperative-vs-declarative anchor on the flow section header so
the eyebrow link is shareable; reveals a # glyph on hover/focus.
- Align the compiled-SQL note dots to the first-line midpoint
(mt-[6px] instead of mt-1) so 4px dots sit at y=8 in a 16px line.
- Remove all ClickHouse references from docs-site (primary-sources,
quickstart, ktx-setup, contributing, agents-setup, mechanics test,
warehouse drivers in the flow diagram).
* test: drop ClickHouse contributing-docs assertion
Align the workspace-package mirror test with the ClickHouse removal
from docs-site (75907eb). The connector-clickhouse package still
exists in packages/, but contributing.mdx no longer lists it, so the
test that mirrored docs against the workspace was failing.
2026-05-19 23:41:29 +02:00
|
|
|
/\/ktx\/brand\/(?:postgresql|snowflake|bigquery|mysql|sqlserver|sqlite|metabase|dbt|looker|notion)\.svg/,
|
2026-05-16 13:25:37 -04:00
|
|
|
);
|
|
|
|
|
assert.doesNotMatch(component, /<img/);
|
2026-05-15 15:31:51 -04:00
|
|
|
assert.doesNotMatch(component, /w-\[calc\(100vw/);
|
|
|
|
|
assert.doesNotMatch(component, /xl:grid-cols-2/);
|
|
|
|
|
assert.doesNotMatch(component, /lg:grid-cols-\[[^\]]*_2rem_/);
|
|
|
|
|
});
|