feat(docs): add Fumadocs site workspace

This commit is contained in:
Luca Martial 2026-05-11 01:08:31 -07:00
parent cb9ab25456
commit 572d515db0
28 changed files with 3979 additions and 84 deletions

View file

@ -0,0 +1,7 @@
import { HomeLayout } from "fumadocs-ui/layouts/home";
import type { ReactNode } from "react";
import { baseOptions } from "@/app/layout.config";
export default function Layout({ children }: { children: ReactNode }) {
return <HomeLayout {...baseOptions}>{children}</HomeLayout>;
}

5
docs/app/(home)/page.tsx Normal file
View file

@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function HomePage() {
redirect("/docs/getting-started/introduction");
}

12
docs/app/docs/layout.tsx Normal file
View file

@ -0,0 +1,12 @@
import { source } from "@/lib/source";
import { DocsLayout } from "fumadocs-ui/layouts/docs";
import type { ReactNode } from "react";
import { baseOptions } from "@/app/layout.config";
export default function Layout({ children }: { children: ReactNode }) {
return (
<DocsLayout tree={source.pageTree} {...baseOptions}>
{children}
</DocsLayout>
);
}

View file

@ -0,0 +1,10 @@
import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
import { Logo } from "@/components/logo";
export const baseOptions: BaseLayoutProps = {
nav: {
title: <Logo />,
transparentMode: "top",
},
githubUrl: "https://github.com/kaelio/ktx",
};

44
docs/app/layout.tsx Normal file
View file

@ -0,0 +1,44 @@
import "./global.css";
import { RootProvider } from "fumadocs-ui/provider";
import { Outfit, Inter, Geist_Mono } from "next/font/google";
import type { ReactNode } from "react";
import type { Metadata } from "next";
const outfit = Outfit({
variable: "--font-outfit",
subsets: ["latin"],
weight: ["400", "500", "600", "700", "800"],
});
const inter = Inter({
variable: "--font-inter",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: {
template: "%s | KTX Docs",
default: "KTX Docs",
},
description:
"Open-source context infrastructure that makes agentic analytics reliable.",
};
export default function RootLayout({ children }: { children: ReactNode }) {
return (
<html
lang="en"
className={`${outfit.variable} ${inter.variable} ${geistMono.variable}`}
suppressHydrationWarning
>
<body>
<RootProvider>{children}</RootProvider>
</body>
</html>
);
}

56
docs/components/logo.tsx Normal file
View file

@ -0,0 +1,56 @@
export function Logo() {
return (
<div className="flex items-center gap-2 group">
<div className="relative flex items-center justify-center transition-transform duration-300 ease-out group-hover:rotate-[-4deg]">
<svg
width="22"
height="22"
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
aria-hidden="true"
>
<defs>
<linearGradient id="ktx-grad-a" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
<stop offset="0%" stopColor="var(--color-fd-primary)" />
<stop offset="100%" stopColor="var(--color-fd-primary)" stopOpacity="0.55" />
</linearGradient>
<linearGradient id="ktx-grad-b" x1="0" y1="0" x2="24" y2="24" gradientUnits="userSpaceOnUse">
<stop offset="0%" stopColor="var(--color-fd-primary)" stopOpacity="0.85" />
<stop offset="100%" stopColor="var(--color-fd-primary)" stopOpacity="0.4" />
</linearGradient>
</defs>
{/* Bottom layer */}
<path
d="M3 17 L12 21.5 L21 17 L12 12.5 Z"
fill="url(#ktx-grad-a)"
opacity="0.4"
/>
{/* Middle layer */}
<path
d="M3 12 L12 16.5 L21 12 L12 7.5 Z"
fill="url(#ktx-grad-b)"
opacity="0.7"
/>
{/* Top layer */}
<path
d="M3 7 L12 11.5 L21 7 L12 2.5 Z"
fill="var(--color-fd-primary)"
/>
</svg>
</div>
<span
className="text-[15px] font-semibold text-fd-foreground tracking-tight"
style={{ fontFamily: "var(--font-display), var(--font-sans), sans-serif" }}
>
KTX
</span>
<span
className="text-[13px] font-medium text-fd-muted-foreground/80 tracking-tight border-l border-fd-border pl-2 ml-0.5"
style={{ fontFamily: "var(--font-display), var(--font-sans), sans-serif" }}
>
Docs
</span>
</div>
);
}

View file

@ -0,0 +1,58 @@
"use client";
import { useEffect, useRef, type ReactNode } from "react";
type Props = {
children: ReactNode;
className?: string;
stagger?: boolean;
threshold?: number;
};
export function ScrollReveal({
children,
className = "",
stagger = false,
threshold = 0.1,
}: Props) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
const node = ref.current;
if (!node) return;
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
if (stagger) {
entry.target.querySelectorAll(".rv").forEach((el) => {
el.classList.add("visible");
});
}
observer.unobserve(entry.target);
}
}
},
{ threshold, rootMargin: "0px 0px -40px 0px" }
);
if (stagger) {
observer.observe(node);
} else {
node.querySelectorAll(".rv").forEach((el) => observer.observe(el));
}
return () => observer.disconnect();
}, [stagger, threshold]);
return (
<div
ref={ref}
className={`${stagger ? "rv rv-stagger" : ""} ${className}`}
>
{children}
</div>
);
}

View file

@ -0,0 +1,56 @@
export function TerminalPreview() {
return (
<div className="terminal-frame sheen w-full max-w-[560px]">
<div className="terminal-head">
<span className="terminal-dot" style={{ background: "#ff5f57" }} />
<span className="terminal-dot" style={{ background: "#febc2e" }} />
<span className="terminal-dot" style={{ background: "#28c840" }} />
<span className="ml-2 text-[11px] text-zinc-500 font-medium tracking-wide">
~/analytics
</span>
</div>
<div className="terminal-body">
<div>
<span className="term-prompt">$</span>{" "}
<span className="term-cmd">ktx setup</span>
</div>
<div className="h-2" />
<div className="term-dim"> Welcome to KTX setup</div>
<div className="term-dim"></div>
<div>
<span className="term-dim"></span>{" "}
<span className="term-key">LLM</span>{" "}
<span className="term-ok"> claude-sonnet-4-6</span>
</div>
<div>
<span className="term-dim"></span>{" "}
<span className="term-key">Embeddings</span>{" "}
<span className="term-ok"> openai · text-embedding-3-small</span>
</div>
<div>
<span className="term-dim"></span>{" "}
<span className="term-key">Database</span>{" "}
<span className="term-ok"> postgres-warehouse · 42 tables</span>
</div>
<div>
<span className="term-dim"></span>{" "}
<span className="term-key">Sources</span>{" "}
<span className="term-ok"> dbt-main · 218 models</span>
</div>
<div className="h-2" />
<div className="term-info"> Building context for agents</div>
<div className="pl-3 text-[12px] term-dim">
enriching schema · detecting relationships · ingesting dbt
</div>
<div className="h-2" />
<div className="term-ok"> KTX context is ready for agents.</div>
<div className="h-2" />
<div>
<span className="term-prompt">$</span>{" "}
<span className="term-cmd">ktx serve</span>
<span className="term-cursor ml-1" />
</div>
</div>
</div>
);
}

View file

@ -88,16 +88,7 @@ The fixture runs in multiple modes to isolate the contribution of each pipeline
## Results
On the default configuration (`accept_threshold: 0.85`, `review_threshold: 0.55`, `llm_proposals: false`):
```text
Accepted: 9
Review: 0
Rejected: 0
Skipped: 0
```
All 9 ground-truth relationships are detected and accepted with no false positives and no relationships left in the review tier. Primary keys are correctly inferred for all 6 tables.
Results for the default configuration will be added after the benchmark run is finalized.
## Reproducing the benchmark

View file

@ -1,5 +1,5 @@
{
"title": "Benchmarks",
"defaultOpen": false,
"defaultOpen": true,
"pages": ["link-detection"]
}

View file

@ -1,6 +1,6 @@
{
"title": "CLI Reference",
"defaultOpen": false,
"defaultOpen": true,
"pages": [
"ktx-setup",
"ktx-connection",

View file

@ -1,5 +1,5 @@
{
"title": "Community",
"defaultOpen": false,
"defaultOpen": true,
"pages": ["contributing"]
}

View file

@ -1,5 +1,5 @@
{
"title": "Concepts",
"defaultOpen": false,
"defaultOpen": true,
"pages": ["the-context-layer", "context-as-code"]
}

View file

@ -1,5 +1,5 @@
{
"title": "Getting Started",
"defaultOpen": false,
"defaultOpen": true,
"pages": ["introduction", "quickstart"]
}

View file

@ -1,5 +1,5 @@
{
"title": "Guides",
"defaultOpen": false,
"defaultOpen": true,
"pages": ["building-context", "writing-context", "serving-agents"]
}

View file

@ -1,5 +1,5 @@
{
"title": "Integrations",
"defaultOpen": false,
"defaultOpen": true,
"pages": ["primary-sources", "context-sources", "agent-clients"]
}

7
docs/lib/source.ts Normal file
View file

@ -0,0 +1,7 @@
import { docs } from "@/.source";
import { loader } from "fumadocs-core/source";
export const source = loader({
source: docs.toFumadocsSource(),
baseUrl: "/docs",
});

6
docs/next-env.d.ts vendored Normal file
View file

@ -0,0 +1,6 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
/// <reference path="./.next/types/routes.d.ts" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

8
docs/next.config.mjs Normal file
View file

@ -0,0 +1,8 @@
import { createMDX } from "fumadocs-mdx/next";
const withMDX = createMDX();
/** @type {import('next').NextConfig} */
const config = {};
export default withMDX(config);

26
docs/package.json Normal file
View file

@ -0,0 +1,26 @@
{
"name": "ktx-docs",
"version": "0.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"fumadocs-core": "15.7.13",
"fumadocs-mdx": "11.10.1",
"fumadocs-ui": "15.7.13",
"next": "^15",
"react": "19.2.6",
"react-dom": "19.2.6"
},
"devDependencies": {
"@types/react": "^19",
"@types/react-dom": "^19",
"typescript": "^5.9",
"@tailwindcss/postcss": "^4",
"tailwindcss": "^4"
}
}

7
docs/postcss.config.mjs Normal file
View file

@ -0,0 +1,7 @@
const config = {
plugins: {
"@tailwindcss/postcss": {},
},
};
export default config;

9
docs/source.config.ts Normal file
View file

@ -0,0 +1,9 @@
import { defineDocs, defineConfig } from "fumadocs-mdx/config";
export const docs = defineDocs({
dir: "content/docs",
});
export default defineConfig({
mdxOptions: {},
});

41
docs/tsconfig.json Normal file
View file

@ -0,0 +1,41 @@
{
"compilerOptions": {
"target": "ESNext",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "ESNext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".source/**/*.ts",
"next-env.d.ts",
".next/types/**/*.ts"
],
"exclude": [
"node_modules"
]
}