diff --git a/src/app/[lang]/docs/[slug]/page.tsx b/src/app/[lang]/docs/[slug]/page.tsx new file mode 100644 index 0000000..5f59d18 --- /dev/null +++ b/src/app/[lang]/docs/[slug]/page.tsx @@ -0,0 +1,67 @@ +import type { Metadata } from "next"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { isLocale, locales, type Locale } from "@/i18n/config"; +import { DOC_NAV, DOC_SLUGS, type DocSlug } from "@/lib/docs"; +import { DOC_COMPONENTS } from "@/components/docs/registry"; + +export function generateStaticParams() { + return locales.flatMap((lang) => DOC_SLUGS.map((slug) => ({ lang, slug }))); +} + +export async function generateMetadata({ + params, +}: { + params: Promise<{ lang: string; slug: string }>; +}): Promise { + const { lang, slug } = await params; + const item = DOC_NAV.find((d) => d.slug === slug); + const locale: Locale = isLocale(lang) ? lang : "en"; + const title = item ? item.title[locale] : "Docs"; + return { title: `${title} — Weftmap` }; +} + +export default async function DocPage({ + params, +}: { + params: Promise<{ lang: string; slug: string }>; +}) { + const { lang, slug } = await params; + if (!isLocale(lang)) notFound(); + + const index = DOC_NAV.findIndex((d) => d.slug === slug); + if (index === -1) notFound(); + + const Content = DOC_COMPONENTS[slug as DocSlug]; + const prev = DOC_NAV[index - 1]; + const next = DOC_NAV[index + 1]; + + return ( +
+ + + +
+ ); +} diff --git a/src/app/[lang]/docs/layout.tsx b/src/app/[lang]/docs/layout.tsx new file mode 100644 index 0000000..538225b --- /dev/null +++ b/src/app/[lang]/docs/layout.tsx @@ -0,0 +1,34 @@ +import type { ReactNode } from "react"; +import { notFound } from "next/navigation"; +import { isLocale, locales, type Locale } from "@/i18n/config"; +import Header from "@/components/layout/Header"; +import DocsSidebar from "@/components/docs/DocsSidebar"; + +export function generateStaticParams() { + return locales.map((lang) => ({ lang })); +} + +export default async function DocsLayout({ + children, + params, +}: { + children: ReactNode; + params: Promise<{ lang: string }>; +}) { + const { lang } = await params; + if (!isLocale(lang)) notFound(); + + return ( + <> +
+
+ +
{children}
+
+ + ); +} diff --git a/src/app/[lang]/docs/page.tsx b/src/app/[lang]/docs/page.tsx new file mode 100644 index 0000000..afbe2b8 --- /dev/null +++ b/src/app/[lang]/docs/page.tsx @@ -0,0 +1,10 @@ +import { redirect } from "next/navigation"; + +export default async function DocsIndex({ + params, +}: { + params: Promise<{ lang: string }>; +}) { + const { lang } = await params; + redirect(`/${lang}/docs/introduction`); +} diff --git a/src/components/docs/DocsSidebar.tsx b/src/components/docs/DocsSidebar.tsx new file mode 100644 index 0000000..4e31603 --- /dev/null +++ b/src/components/docs/DocsSidebar.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Link from "next/link"; +import { usePathname } from "next/navigation"; +import type { Locale } from "@/i18n/config"; +import { DOC_NAV } from "@/lib/docs"; + +export default function DocsSidebar({ lang }: { lang: Locale }) { + const pathname = usePathname(); + + return ( + + ); +} diff --git a/src/components/docs/Prose.tsx b/src/components/docs/Prose.tsx new file mode 100644 index 0000000..d1f4025 --- /dev/null +++ b/src/components/docs/Prose.tsx @@ -0,0 +1,112 @@ +import type { ReactNode } from "react"; + +// Shared typography primitives for documentation pages — keeps every page +// visually consistent without pulling in a markdown/MDX toolchain. + +export function H1({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function Lead({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function H2({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function H3({ children }: { children: ReactNode }) { + return ( +

+ {children} +

+ ); +} + +export function P({ children }: { children: ReactNode }) { + return

{children}

; +} + +export function UL({ children }: { children: ReactNode }) { + return ( + + ); +} + +export function LI({ children }: { children: ReactNode }) { + return ( +
  • + + {children} +
  • + ); +} + +export function Code({ children }: { children: ReactNode }) { + return ( + + {children} + + ); +} + +export function CodeBlock({ children, label }: { children: ReactNode; label?: string }) { + return ( +
    + {label && ( +
    + {label} +
    + )} +
    +        {children}
    +      
    +
    + ); +} + +const DOT_COLORS = { + calls: "rgba(255,255,255,0.5)", + imports: "#5eead4", + extends: "#c4b5fd", +} as const; + +export function EdgeDot({ kind }: { kind: "calls" | "imports" | "extends" }) { + return ( + + ); +} + +const CALLOUT_STYLES = { + note: "border-teal-300/25 bg-teal-300/[0.05]", + warn: "border-amber-300/25 bg-amber-300/[0.05]", +} as const; + +export function Callout({ + children, + kind = "note", +}: { + children: ReactNode; + kind?: "note" | "warn"; +}) { + return ( +
    + {children} +
    + ); +} diff --git a/src/components/docs/pages/GettingStarted.tsx b/src/components/docs/pages/GettingStarted.tsx new file mode 100644 index 0000000..ba547cb --- /dev/null +++ b/src/components/docs/pages/GettingStarted.tsx @@ -0,0 +1,98 @@ +import type { Locale } from "@/i18n/config"; +import { H1, Lead, H2, H3, P, UL, LI, Code, Callout } from "../Prose"; + +export default function GettingStarted({ lang }: { lang: Locale }) { + if (lang === "es") { + return ( + <> +

    Empezar

    + + Hay dos formas de generar un grafo: pegar un fragmento suelto, o subir + una carpeta para mapear todo un proyecto. + + +

    Modo Snippet

    +

    Ideal para un archivo o un ejemplo rápido.

    +
      +
    • Abre la app y deja la pestaña Snippet activa.
    • +
    • Elige el lenguaje en el selector inferior.
    • +
    • Pega tu código en el editor.
    • +
    • Pulsa Analizar (o ⌘/Ctrl + Enter).
    • +
    +

    El grafo aparece en el panel derecho. Cambiar de lenguaje carga un ejemplo nuevo solo si no has tocado el editor.

    + +

    Modo Proyecto

    +

    Para ver la arquitectura de varios archivos a la vez.

    +
      +
    • Cambia a la pestaña Proyecto.
    • +
    • Pulsa Subir una carpeta y elige el directorio.
    • +
    • Weftmap filtra por extensión del lenguaje e ignora node_modules, .git y similares.
    • +
    • Revisa la lista de archivos y pulsa Analizar.
    • +
    + + Todo ocurre en tu navegador y servidor; los archivos no se guardan. Hay + un límite de tamaño y de número de archivos para mantenerlo ágil. + + +

    Explorar el diagrama

    +
      +
    • Mover y zoom: arrastra el lienzo, rueda para acercar.
    • +
    • Encuadrar: usa los controles para reajustar la vista.
    • +
    • Filtrar: en la leyenda, oculta o muestra calls, imports y extends.
    • +
    + +

    ¿No aparece nada?

    +
      +
    • Verifica que el lenguaje seleccionado coincide con el código.
    • +
    • Solo se dibujan funciones definidas en el código; las llamadas a librerías se omiten.
    • +
    + + ); + } + + return ( + <> +

    Getting started

    + + There are two ways to build a graph: paste a single snippet, or upload a + folder to map a whole project. + + +

    Snippet mode

    +

    Great for one file or a quick example.

    +
      +
    • Open the app and keep the Snippet tab active.
    • +
    • Pick the language in the bottom selector.
    • +
    • Paste your code into the editor.
    • +
    • Hit Analyze (or ⌘/Ctrl + Enter).
    • +
    +

    The graph appears in the right panel. Switching language loads a fresh sample only if you haven't edited the editor.

    + +

    Project mode

    +

    To see the architecture across many files at once.

    +
      +
    • Switch to the Project tab.
    • +
    • Click Upload a folder and pick the directory.
    • +
    • Weftmap filters by the language's extensions and ignores node_modules, .git and the like.
    • +
    • Review the file list and hit Analyze.
    • +
    + + Everything runs in your browser and server; files are not stored. There is + a size and file-count limit to keep things fast. + + +

    Explore the diagram

    +
      +
    • Pan & zoom: drag the canvas, scroll to zoom.
    • +
    • Fit: use the controls to reset the view.
    • +
    • Filter: in the legend, toggle calls, imports and extends.
    • +
    + +

    Nothing shows up?

    +
      +
    • Make sure the selected language matches the code.
    • +
    • Only functions defined in the code are drawn; calls to libraries are skipped.
    • +
    + + ); +} diff --git a/src/components/docs/pages/HowItWorks.tsx b/src/components/docs/pages/HowItWorks.tsx new file mode 100644 index 0000000..afbb37e --- /dev/null +++ b/src/components/docs/pages/HowItWorks.tsx @@ -0,0 +1,94 @@ +import type { Locale } from "@/i18n/config"; +import { H1, Lead, H2, P, UL, LI, Code, EdgeDot, Callout } from "../Prose"; + +export default function HowItWorks({ lang }: { lang: Locale }) { + if (lang === "es") { + return ( + <> +

    Cómo funciona

    + + Weftmap hace análisis estático: lee tu código sin ejecutarlo y deduce las + relaciones a partir del árbol de sintaxis. + + +

    El pipeline

    +
      +
    • Parseo — cada archivo se analiza con tree-sitter, que produce un árbol de sintaxis real (no expresiones regulares).
    • +
    • Tabla de símbolos — se recogen las funciones y clases definidas en cada archivo.
    • +
    • Imports — se resuelve cada import al archivo correspondiente, según las reglas del lenguaje.
    • +
    • Llamadas — cada llamada se enlaza a su definición: primero local, luego en un archivo importado, y si solo hay un candidato global, a ese.
    • +
    • Clases y herencia — los métodos se asignan a su clase y se trazan las relaciones extends.
    • +
    • Layout — se agrupa en módulos › clases › métodos y se posiciona con un layout jerárquico.
    • +
    + +

    Modelo del grafo

    +

    Tres tipos de nodo (módulo, función, clase) y tres tipos de arista:

    +
      +
    • calls — entre funciones/métodos.
    • +
    • imports — entre módulos.
    • +
    • extends — entre clases.
    • +
    + +

    Límites conocidos

    +

    El análisis es por nombre, no por tipo ni alcance. Eso implica:

    +
      +
    • Dos funciones (o métodos de clases distintas) con el mismo nombre se fusionan en un nodo.
    • +
    • No se sigue el despacho dinámico, eval, reflexión ni callbacks invocados indirectamente.
    • +
    • Las llamadas a librerías externas se omiten (solo se enlazan definiciones de tu código).
    • +
    • En Go los imports por paquete no se mapean a archivos.
    • +
    • Por ahora se sube una carpeta (aún no archivos .zip).
    • +
    + + + Trata el grafo como un mapa de alta señal, no como una verdad exacta: + el análisis estático por nombre acierta en la mayoría de casos comunes, + pero puede aproximar en código muy dinámico. + + + ); + } + + return ( + <> +

    How it works

    + + Weftmap does static analysis: it reads your code without running it and + infers the relationships from the syntax tree. + + +

    The pipeline

    +
      +
    • Parsing — each file is parsed with tree-sitter, producing a real syntax tree (not regex).
    • +
    • Symbol table — the functions and classes defined in each file are collected.
    • +
    • Imports — each import is resolved to its file, following the language's rules.
    • +
    • Calls — each call is linked to its definition: local first, then an imported file, and if there is a single global candidate, that one.
    • +
    • Classes & inheritance — methods are attributed to their class and extends relationships are traced.
    • +
    • Layout — grouped into modules › classes › methods and positioned with a hierarchical layout.
    • +
    + +

    Graph model

    +

    Three node types (module, function, class) and three edge types:

    +
      +
    • calls — between functions/methods.
    • +
    • imports — between modules.
    • +
    • extends — between classes.
    • +
    + +

    Known limitations

    +

    Resolution is name-based, not type- or scope-aware. That means:

    +
      +
    • Two functions (or methods of different classes) with the same name merge into one node.
    • +
    • Dynamic dispatch, eval, reflection and indirectly-invoked callbacks are not followed.
    • +
    • Calls to external libraries are skipped (only definitions in your code are linked).
    • +
    • In Go, package-based imports are not mapped to files.
    • +
    • For now you upload a folder (no .zip files yet).
    • +
    + + + Treat the graph as a high-signal map, not exact truth: name-based static + analysis is right for most common cases but can approximate on very dynamic + code. + + + ); +} diff --git a/src/components/docs/pages/Introduction.tsx b/src/components/docs/pages/Introduction.tsx new file mode 100644 index 0000000..6ea2a4b --- /dev/null +++ b/src/components/docs/pages/Introduction.tsx @@ -0,0 +1,91 @@ +import type { Locale } from "@/i18n/config"; +import { H1, Lead, H2, P, UL, LI, Code, EdgeDot, Callout } from "../Prose"; + +export default function Introduction({ lang }: { lang: Locale }) { + if (lang === "es") { + return ( + <> +

    Introducción

    + + Weftmap convierte tu código en un grafo de llamadas interactivo. Pega un + fragmento o sube un proyecto y visualiza cómo se relacionan funciones, + clases y módulos. + + +

    ¿Qué es Weftmap?

    +

    + Weftmap lee tu código con tree-sitter y dibuja un mapa + navegable: quién llama a quién, qué archivo importa a cuál y qué clase + hereda de otra. No ejecuta el código — solo lo analiza estáticamente. +

    + +

    Qué muestra el grafo

    +

    Los nodos son las piezas de tu código:

    +
      +
    • Módulos — cada archivo, dibujado como contenedor.
    • +
    • Funciones y métodos — dentro de su módulo o clase.
    • +
    • Clases — agrupan sus métodos.
    • +
    +

    Las aristas son las relaciones:

    +
      +
    • calls — una función llama a otra.
    • +
    • imports — un módulo importa a otro.
    • +
    • extends — una clase hereda de otra.
    • +
    + +

    Cómo leerlo

    +
      +
    • Arrastra para moverte por el lienzo y usa la rueda para hacer zoom.
    • +
    • La leyenda (arriba a la izquierda) filtra cada tipo de arista con un clic.
    • +
    • Los recuadros agrupan: los módulos contienen clases y funciones; las clases contienen sus métodos.
    • +
    + + + ¿Listo para probar? Continúa con Empezar. + + + ); + } + + return ( + <> +

    Introduction

    + + Weftmap turns your code into an interactive call graph. Paste a snippet or + upload a project and see how functions, classes and modules relate. + + +

    What is Weftmap?

    +

    + Weftmap reads your code with tree-sitter and draws a navigable + map: who calls whom, which file imports which, and which class extends + another. It never runs your code — it analyzes it statically. +

    + +

    What the graph shows

    +

    The nodes are the pieces of your code:

    +
      +
    • Modules — each file, drawn as a container.
    • +
    • Functions and methods — inside their module or class.
    • +
    • Classes — group their methods.
    • +
    +

    The edges are the relationships:

    +
      +
    • calls — one function calls another.
    • +
    • imports — one module imports another.
    • +
    • extends — one class extends another.
    • +
    + +

    How to read it

    +
      +
    • Drag to pan the canvas and scroll to zoom.
    • +
    • The legend (top-left) toggles each edge type with a click.
    • +
    • Boxes group things: modules contain classes and functions; classes contain their methods.
    • +
    + + + Ready to try it? Continue with Getting started. + + + ); +} diff --git a/src/components/docs/pages/Languages.tsx b/src/components/docs/pages/Languages.tsx new file mode 100644 index 0000000..a380694 --- /dev/null +++ b/src/components/docs/pages/Languages.tsx @@ -0,0 +1,107 @@ +import type { Locale } from "@/i18n/config"; +import { H1, Lead, H2, UL, LI, Code, Callout } from "../Prose"; + +type Row = { lang: string; fns: boolean; classes: boolean; imports: boolean; extends: boolean }; + +const ROWS: Row[] = [ + { lang: "Python", fns: true, classes: true, imports: true, extends: true }, + { lang: "JavaScript", fns: true, classes: true, imports: true, extends: true }, + { lang: "TypeScript", fns: true, classes: true, imports: true, extends: true }, + { lang: "Go", fns: true, classes: false, imports: false, extends: false }, +]; + +function Cell({ on }: { on: boolean }) { + return ( + + {on ? ( + + ) : ( + + )} + + ); +} + +function Matrix({ headers }: { headers: string[] }) { + return ( +
    + + + + + + + + + + + + {ROWS.map((r) => ( + + + + + + + + ))} + +
    {headers[0]}{headers[1]}{headers[2]}{headers[3]}{headers[4]}
    {r.lang}
    +
    + ); +} + +export default function Languages({ lang }: { lang: Locale }) { + if (lang === "es") { + return ( + <> +

    Lenguajes

    + + Weftmap soporta cuatro lenguajes hoy. La arquitectura es ampliable: cada + lenguaje es una gramática de tree-sitter más un conjunto de consultas. + + + + +

    Notas por lenguaje

    +
      +
    • Python — funciones, clases y métodos; resuelve import y from … import (incluidos relativos) y la herencia entre clases.
    • +
    • JavaScript — funciones (incluidas arrow asignadas), clases y métodos; imports ES relativos (./x) y extends.
    • +
    • TypeScript — igual que JavaScript pero con la gramática dedicada, así las anotaciones de tipos se parsean sin problemas.
    • +
    • Go — funciones y métodos. Go no tiene clases, así que no hay nodos de clase ni herencia.
    • +
    + + + En Go los imports son por paquete (no por archivo), así que no se dibujan + aristas de import. Las llamadas entre archivos del mismo paquete sí se + resuelven por nombre. + + + ); + } + + return ( + <> +

    Languages

    + + Weftmap supports four languages today. The architecture is extensible: each + language is a tree-sitter grammar plus a set of queries. + + + + +

    Per-language notes

    +
      +
    • Python — functions, classes and methods; resolves import and from … import (including relative) and class inheritance.
    • +
    • JavaScript — functions (including assigned arrow functions), classes and methods; relative ES imports (./x) and extends.
    • +
    • TypeScript — same as JavaScript but with the dedicated grammar, so type annotations parse cleanly.
    • +
    • Go — functions and methods. Go has no classes, so there are no class nodes or inheritance.
    • +
    + + + In Go, imports are package-based (not file-based), so import edges are not + drawn. Calls between files in the same package are still resolved by name. + + + ); +} diff --git a/src/components/docs/registry.ts b/src/components/docs/registry.ts new file mode 100644 index 0000000..0ddf90b --- /dev/null +++ b/src/components/docs/registry.ts @@ -0,0 +1,16 @@ +import type { ReactNode } from "react"; +import type { Locale } from "@/i18n/config"; +import type { DocSlug } from "@/lib/docs"; +import Introduction from "./pages/Introduction"; +import GettingStarted from "./pages/GettingStarted"; +import Languages from "./pages/Languages"; +import HowItWorks from "./pages/HowItWorks"; + +type DocComponent = (props: { lang: Locale }) => ReactNode; + +export const DOC_COMPONENTS: Record = { + introduction: Introduction, + "getting-started": GettingStarted, + languages: Languages, + "how-it-works": HowItWorks, +}; diff --git a/src/components/layout/Header.tsx b/src/components/layout/Header.tsx index c80646b..0f2a4f6 100644 --- a/src/components/layout/Header.tsx +++ b/src/components/layout/Header.tsx @@ -5,7 +5,6 @@ import Link from "next/link"; import { locales, type Locale } from "@/i18n/config"; const REPO = "https://github.com/DataDave-Dev/weftmap"; -const DOCS = `${REPO}/blob/main/README.md`; const linkClass = "flex items-center gap-1.5 px-3 py-1.5 rounded-full text-[13px] text-muted hover:text-fg hover:bg-white/[0.06] transition-colors"; @@ -41,7 +40,7 @@ export default function Header({ lang }: { lang: Locale }) {