diff --git a/docs/app/components/diagrams/arch-stack.tsx b/docs/app/components/diagrams/arch-stack.tsx index 8030b5f9..f0f3b1a7 100644 --- a/docs/app/components/diagrams/arch-stack.tsx +++ b/docs/app/components/diagrams/arch-stack.tsx @@ -1,12 +1,14 @@ import { type CSSProperties, Fragment, type ReactNode } from "react"; +import { SdkBinding, SdkName, SdkSwap } from "@/components/sdk-text"; // Layered architecture stack — exact port of the prototype's `.archstack` HTML -// (semantic layers + PyO3/store boundaries as direct children). +// (semantic layers + binding/store boundaries as direct children). The +// user-facing layer adapts to the active SDK via the SDK-text atoms. type Kind = "py" | "rust" | "store" | ""; interface Layer { - tag: string; + tag: ReactNode; /** Pill colour: `.ltag t-*`. */ tagKind?: Kind; /** `.layer` modifier; defaults to `tagKind`. The prototype sometimes pairs a @@ -68,13 +70,25 @@ export function ArchitectureStack() { } layers={[ { - tag: "Python", + tag: , tagKind: "py", title: "User-facing API", body: ( <> - Queue, @task, .delay(), - results, workflows, resources — the surface you write against. + Queue,{" "} + + @task, .delay() + + } + node={ + <> + .task(), .enqueue() + + } + /> + , results, workflows, resources — the surface you write against. ), role: "what you write", @@ -83,7 +97,12 @@ export function ArchitectureStack() { tag: "Rust", tagKind: "rust", title: "Engine", - body: "Scheduler, dispatcher, worker pool, rate limiter — Tokio runtime, OS-thread pool, near-zero Python overhead.", + body: ( + <> + Scheduler, dispatcher, worker pool, rate limiter — Tokio runtime, + OS-thread pool, near-zero overhead. + + ), role: "where the work runs", }, { @@ -97,7 +116,9 @@ export function ArchitectureStack() { bounds={[ <> - PyO3 boundary + + boundary + , <> @@ -135,8 +156,19 @@ export function ResourcePipeline() { <> ResourceRuntime initializes resources at worker startup in topological order, then injects requested ones (via{" "} - inject= or Inject["name"]) as kwargs. - Task-scoped resources come from a semaphore pool. + + inject= or Inject["name"] + + } + node={ + <> + inject: or useResource() + + } + /> + ). Task-scoped resources come from a semaphore pool. ), }, diff --git a/docs/app/components/diagrams/worker-fork.tsx b/docs/app/components/diagrams/worker-fork.tsx index 1820d8f0..d7118053 100644 --- a/docs/app/components/diagrams/worker-fork.tsx +++ b/docs/app/components/diagrams/worker-fork.tsx @@ -1,6 +1,8 @@ +import { SdkSwap } from "@/components/sdk-text"; + // Worker pool — exact port of the prototype's `.archstack` + `.fork-route` + // `.archfork` (scheduler routes by task type; sync OS-thread pool vs async pool). -// `architecture/worker-pool.mdx`. +// The per-runtime details adapt to the active SDK. `architecture/worker-pool.mdx`. export function WorkerDispatch() { return (
@@ -11,12 +13,18 @@ export function WorkerDispatch() {
Dequeues a job, applies rate limits, then routes it{" "} by task type — sync functions to the thread pool,{" "} - async def functions to the async runtime. + async def} + node={async} + />{" "} + functions to the async runtime.
routes by task type -
sync def  ·  async def
+
+ +
bounded mpsc · workers × 2
@@ -25,9 +33,23 @@ export function WorkerDispatch() {
OS-thread workers
- Each sync worker is a Rust std::thread. The GIL is - acquired per task via Python::with_gil() — - independent across workers. + + Each sync worker is a Rust std::thread. The + GIL is acquired per task via{" "} + Python::with_gil() — independent across + workers. + + } + node={ + <> + Sync handlers run on an OS-thread pool owned by the Rust + core — independent across workers, with no event loop to + block. + + } + />
@@ -37,11 +59,26 @@ export function WorkerDispatch() {
Async pool
-
NativeAsyncPool
+
+ +
- async def tasks are dispatched to an{" "} - AsyncTaskExecutor on a Python daemon thread;{" "} - PyResultSender bridges results back. + + async def tasks are dispatched to an{" "} + AsyncTaskExecutor on a Python daemon thread;{" "} + PyResultSender bridges results back. + + } + node={ + <> + async handlers run on a native async pool — + no thread per job; each runs on your Node event loop and + its promise is awaited back into the core. + + } + />
diff --git a/docs/app/components/docs/sidebar.tsx b/docs/app/components/docs/sidebar.tsx index 794e3556..a59d7261 100644 --- a/docs/app/components/docs/sidebar.tsx +++ b/docs/app/components/docs/sidebar.tsx @@ -6,6 +6,7 @@ import { type NavNode, navForSdk, type Sdk, + sdkLabels, sdkSwitchTarget, } from "@/lib"; @@ -16,10 +17,9 @@ function containsHref(node: NavNode, current: string): boolean { ); } -const SDK_LABELS: { id: Sdk; label: string }[] = [ - { id: "python", label: "Python" }, - { id: "node", label: "Node.js" }, -]; +// Switcher options come from the SDK registry, so a new language appears here +// automatically. +const SDK_LABELS = sdkLabels(); /** Global SDK toggle. Sets the shared store (flips inline variants + this nav); * on an SDK-specific page it also navigates to the counterpart page, on a shared diff --git a/docs/app/components/landing/footer.tsx b/docs/app/components/landing/footer.tsx index 84ae03c6..3cd61be0 100644 --- a/docs/app/components/landing/footer.tsx +++ b/docs/app/components/landing/footer.tsx @@ -1,5 +1,6 @@ import { Link } from "react-router"; import { useActiveSdk } from "@/hooks"; +import { VERSION } from "@/lib/version"; type FootLink = { label: string; @@ -95,7 +96,7 @@ export function Footer() {
© 2026 ByteVeda · taskito - MIT License · v0.16 + MIT License · v{VERSION}
); diff --git a/docs/app/components/landing/hero.tsx b/docs/app/components/landing/hero.tsx index 216e559f..8853e8ef 100644 --- a/docs/app/components/landing/hero.tsx +++ b/docs/app/components/landing/hero.tsx @@ -2,10 +2,9 @@ import { useState } from "react"; import { Link } from "react-router"; import { RawHtml } from "@/components/ui"; import { useSdk } from "@/hooks"; +import { sdkProfile } from "@/lib"; import { highlightPython, highlightTs } from "@/lib/highlight-lite"; -import { HERO_PANES } from "@/lib/landing-content"; - -type Lang = "py" | "ts"; +import { HERO_COMING_SOON, HERO_PANES } from "@/lib/landing-content"; function CopyButton({ text }: { text: string }) { const [copied, setCopied] = useState(false); @@ -26,27 +25,26 @@ function CopyButton({ text }: { text: string }) { export function Hero() { const { sdk, setSdk } = useSdk(); - // py/ts mirror the global SDK so the hero tab and the docs sidebar switch stay in sync. - const isNode = sdk === "node"; - const lang: Lang = isNode ? "ts" : "py"; - const active = HERO_PANES.find((p) => p.id === lang) ?? HERO_PANES[0]; + // The selected snippet IS the global SDK — clicking a tab sets it, so the hero + // copy, the install/quickstart links, and the docs sidebar switch all follow. + const active = HERO_PANES.find((p) => p.sdk === sdk) ?? HERO_PANES[0]; + const label = sdkProfile(active.sdk).label; const codeHtml = - lang === "ts" ? highlightTs(active.code) : highlightPython(active.code); + active.lang === "ts" + ? highlightTs(active.code) + : highlightPython(active.code); return (

One queue. - - Built for {isNode ? "Node.js." : "Python."} - + Built for {label}.

- A Rust-powered task queue with a first-class{" "} - {isNode ? "Node.js" : "Python"} SDK over one core and one store - — no broker. Start on SQLite, scale to{" "} - Postgres. + A Rust-powered task queue with a first-class {label} SDK over + one core and one store — no broker. Start on SQLite, + scale to Postgres.

@@ -62,7 +60,7 @@ export function Hero() {
Brokerless Rust core - {isNode ? "Node.js SDK" : "Python SDK"} + {label} SDK DAG workflows
@@ -86,18 +84,21 @@ export function Hero() {
{HERO_PANES.map((p) => ( + ))} + {HERO_COMING_SOON.map((name) => ( + ))} -
@@ -119,15 +120,11 @@ export function Hero() {
- - Read the Python quickstart → - - - Read the Node.js quickstart → - + {HERO_PANES.map((p) => ( + + {p.docLabel} → + + ))}
diff --git a/docs/app/components/landing/sections.tsx b/docs/app/components/landing/sections.tsx index 404859f2..1658890b 100644 --- a/docs/app/components/landing/sections.tsx +++ b/docs/app/components/landing/sections.tsx @@ -154,7 +154,6 @@ export function HowItWorks() { ))}
- result written back to the store
@@ -397,6 +396,7 @@ function InstallPill({ cmd }: { cmd: string }) { } export function CTA() { + const sdk = useActiveSdk(); return (
@@ -414,7 +414,7 @@ export function CTA() {
- + Start the quickstart → diff --git a/docs/app/components/mdx/index.tsx b/docs/app/components/mdx/index.tsx index 4f859a3f..bc46b8fa 100644 --- a/docs/app/components/mdx/index.tsx +++ b/docs/app/components/mdx/index.tsx @@ -2,6 +2,7 @@ import type { MDXComponents } from "mdx/types"; import type { ComponentProps } from "react"; import { Link } from "react-router"; import * as Diagrams from "@/components/diagrams"; +import { SdkBinding, SdkLang, SdkName, SdkSwap } from "@/components/sdk-text"; import { Callout } from "./callout"; import { Card, Cards } from "./card"; import { CodeBlock } from "./code-block"; @@ -43,6 +44,10 @@ export const mdxComponents: MDXComponents = { CodeTabs, SdkOnly, SdkLink, + SdkName, + SdkLang, + SdkBinding, + SdkSwap, Card, Cards, ...Diagrams, diff --git a/docs/app/components/mdx/sdk.tsx b/docs/app/components/mdx/sdk.tsx index 5caa923e..f97fbe37 100644 --- a/docs/app/components/mdx/sdk.tsx +++ b/docs/app/components/mdx/sdk.tsx @@ -9,8 +9,7 @@ import { } from "react"; import { Link } from "react-router"; import { type Sdk, useSdk } from "@/hooks"; - -const LABELS: Record = { python: "Python", node: "Node.js" }; +import { SDK_PROFILES } from "@/lib"; /** Show children only under one SDK. All variants ship in the HTML; the inactive * one is hidden by CSS off `` — no flash, no hydration mismatch. */ @@ -87,7 +86,7 @@ export function CodeTabs({ children }: { children: ReactNode }) { onClick={() => setSdk(variant)} onKeyDown={(e) => onKeyDown(e, index)} > - {LABELS[variant]} + {SDK_PROFILES[variant].label} ); })} diff --git a/docs/app/components/mermaid.tsx b/docs/app/components/mermaid.tsx index 4bb8e8c9..80e64a5a 100644 --- a/docs/app/components/mermaid.tsx +++ b/docs/app/components/mermaid.tsx @@ -1,121 +1,11 @@ import { useEffect, useId, useRef, useState } from "react"; +import { + applyDiagramTheme, + diagramThemeCss, + diagramThemeVariables, +} from "@/lib/mermaid-theme"; import { useThemeMode } from "@/lib/theme"; -const DARK_THEME_VARIABLES = { - darkMode: true, - background: "transparent", - primaryColor: "#1f2024", - primaryTextColor: "#f5f5f7", - primaryBorderColor: "#7a7d85", - lineColor: "#9b9ea6", - secondaryColor: "#2a2c31", - tertiaryColor: "#26282d", - mainBkg: "#1f2024", - nodeBkg: "#1f2024", - nodeBorder: "#7a7d85", - clusterBkg: "#1a1b1e", - clusterBorder: "#5a5d65", - edgeLabelBackground: "#2a2c31", - titleColor: "#f5f5f7", - labelTextColor: "#f5f5f7", - textColor: "#f5f5f7", - noteBkgColor: "#3a3c41", - noteTextColor: "#f5f5f7", - noteBorderColor: "#7a7d85", - errorBkgColor: "#4a1d1d", - errorTextColor: "#fca5a5", - // erDiagram-specific (entity attribute rows) — same color, no zebra stripe - attributeBackgroundColorOdd: "#1f2024", - attributeBackgroundColorEven: "#1f2024", - // stateDiagram-specific - altBackground: "#26282d", - // flowchart label colors - labelBackground: "#2a2c31", - // sequenceDiagram - actorBkg: "#1f2024", - actorBorder: "#7a7d85", - actorTextColor: "#f5f5f7", - actorLineColor: "#9b9ea6", - signalColor: "#f5f5f7", - signalTextColor: "#f5f5f7", - labelBoxBkgColor: "#2a2c31", - labelBoxBorderColor: "#7a7d85", - loopTextColor: "#f5f5f7", - activationBkgColor: "#3a3c41", - activationBorderColor: "#9b9ea6", -} as const; - -const DARK_THEME_CSS = ` - .er.entityBox { fill: #1f2024 !important; stroke: #7a7d85 !important; } - .er.entityLabel { fill: #f5f5f7 !important; } - .er.attributeBoxOdd { fill: #1f2024 !important; } - .er.attributeBoxEven { fill: #1f2024 !important; } - .er .er.attribute-text, - .er .attribute-text, - .er text { - fill: #f5f5f7 !important; - } - .er.relationshipLabel, - .er.relationshipLabelBox { - fill: #f5f5f7 !important; - } - .er.relationshipLabelBox + text { fill: #1a1a1a !important; } -`; - -const LIGHT_THEME_CSS = ` - .er.entityBox { fill: #ffffff !important; stroke: #3a3a3a !important; } - .er.entityLabel { fill: #1a1a1a !important; } - .er.attributeBoxOdd { fill: #ffffff !important; } - .er.attributeBoxEven { fill: #f4f4f5 !important; } - .er .er.attribute-text, - .er .attribute-text, - .er text { - fill: #1a1a1a !important; - } -`; - -const LIGHT_THEME_VARIABLES = { - darkMode: false, - background: "transparent", - primaryColor: "#ffffff", - primaryTextColor: "#1a1a1a", - primaryBorderColor: "#3a3a3a", - lineColor: "#4a4a4a", - secondaryColor: "#f4f4f5", - tertiaryColor: "#fafafa", - mainBkg: "#ffffff", - nodeBkg: "#ffffff", - nodeBorder: "#3a3a3a", - clusterBkg: "#f4f4f5", - clusterBorder: "#a1a1aa", - edgeLabelBackground: "#ffffff", - titleColor: "#1a1a1a", - labelTextColor: "#1a1a1a", - textColor: "#1a1a1a", - noteBkgColor: "#fef9c3", - noteTextColor: "#1a1a1a", - noteBorderColor: "#a1a1aa", - // erDiagram-specific (entity attribute rows) - attributeBackgroundColorOdd: "#ffffff", - attributeBackgroundColorEven: "#f4f4f5", - // stateDiagram-specific - altBackground: "#f4f4f5", - // flowchart label colors - labelBackground: "#ffffff", - // sequenceDiagram - actorBkg: "#ffffff", - actorBorder: "#3a3a3a", - actorTextColor: "#1a1a1a", - actorLineColor: "#4a4a4a", - signalColor: "#1a1a1a", - signalTextColor: "#1a1a1a", - labelBoxBkgColor: "#ffffff", - labelBoxBorderColor: "#3a3a3a", - loopTextColor: "#1a1a1a", - activationBkgColor: "#f4f4f5", - activationBorderColor: "#4a4a4a", -} as const; - export function Mermaid({ chart }: { chart: string }) { const id = useId(); const containerRef = useRef(null); @@ -129,12 +19,12 @@ export function Mermaid({ chart }: { chart: string }) { if (typeof document !== "undefined" && document.fonts?.ready) { await document.fonts.ready; } - const isDark = resolvedTheme === "dark"; + const theme = resolvedTheme === "dark" ? "dark" : "light"; mermaid.initialize({ startOnLoad: false, theme: "base", - themeVariables: isDark ? DARK_THEME_VARIABLES : LIGHT_THEME_VARIABLES, - themeCSS: isDark ? DARK_THEME_CSS : LIGHT_THEME_CSS, + themeVariables: diagramThemeVariables(theme), + themeCSS: diagramThemeCss(theme), securityLevel: "loose", fontFamily: '"IBM Plex Sans", "Inter", system-ui, sans-serif', flowchart: { padding: 18, htmlLabels: true, useMaxWidth: true }, @@ -146,7 +36,10 @@ export function Mermaid({ chart }: { chart: string }) { }); try { const renderId = `m${id.replace(/[^a-zA-Z0-9]/g, "")}`; - const { svg: rendered } = await mermaid.render(renderId, chart); + const { svg: rendered } = await mermaid.render( + renderId, + applyDiagramTheme(chart, theme), + ); if (!cancelled) { setSvg(rendered); } diff --git a/docs/app/components/sdk-text.tsx b/docs/app/components/sdk-text.tsx new file mode 100644 index 00000000..d479e313 --- /dev/null +++ b/docs/app/components/sdk-text.tsx @@ -0,0 +1,48 @@ +import type { ReactNode } from "react"; +import { SDK_IDS, SDK_PROFILES, type Sdk, type SdkProfile } from "@/lib"; + +// Inline SDK-aware text atoms for shared pages + arch diagrams. No-flash: every +// SDK's text ships and CSS hides the inactive ones (off ``, like +// ``) — no re-render, no hydration flash, all variants indexable. + +/** Render one span per SDK, picking a value from each profile; CSS shows only + * the active SDK's span. */ +function SdkVariants({ pick }: { pick: (profile: SdkProfile) => ReactNode }) { + return ( + <> + {SDK_IDS.map((id) => ( + + {pick(SDK_PROFILES[id])} + + ))} + + ); +} + +/** Active SDK's display name, e.g. "Python" / "Node.js". */ +export function SdkName() { + return p.label} />; +} + +/** Active SDK's language name for prose, e.g. "Python" / "Node.js". */ +export function SdkLang() { + return p.language} />; +} + +/** Active SDK's FFI boundary into the Rust core, e.g. "PyO3" / "N-API". */ +export function SdkBinding() { + return p.binding} />; +} + +/** Inline value that differs per SDK, e.g. + * `@task} node={.task()} />`. + * Missing SDK falls back to `fallback` → default SDK → first provided. */ +export function SdkSwap({ + fallback, + ...bySdk +}: Partial> & { fallback?: ReactNode }) { + const provided = SDK_IDS.map((id) => bySdk[id]).find((v) => v !== undefined); + const resolve = (id: Sdk): ReactNode => + bySdk[id] ?? fallback ?? provided ?? null; + return resolve(p.id)} />; +} diff --git a/docs/app/components/ui/site-nav.tsx b/docs/app/components/ui/site-nav.tsx index f5400e43..c57d1c47 100644 --- a/docs/app/components/ui/site-nav.tsx +++ b/docs/app/components/ui/site-nav.tsx @@ -1,6 +1,7 @@ import { Menu, Search } from "lucide-react"; import { Link } from "react-router"; import { ThemeToggle } from "@/components/ui/theme-toggle"; +import { useActiveSdk } from "@/hooks"; // lucide dropped brand glyphs, so the GitHub mark is inlined. function GithubMark() { @@ -17,11 +18,13 @@ function GithubMark() { ); } -const LINKS = [ - { label: "Getting Started", href: "/python/getting-started/installation" }, - { label: "Guides", href: "/python/guides" }, +// `sdk` links are SDK-relative (prefixed with the active /python|/node); the rest +// are shared, SDK-neutral pages. +const LINKS: { label: string; href: string; sdk?: boolean }[] = [ + { label: "Getting Started", href: "getting-started/installation", sdk: true }, + { label: "Guides", href: "guides", sdk: true }, { label: "Architecture", href: "/architecture" }, - { label: "API", href: "/python/api-reference" }, + { label: "API", href: "api-reference", sdk: true }, { label: "Changelog", href: "/resources/changelog" }, ]; @@ -35,6 +38,7 @@ export function SiteNav({ onSearch?: () => void; onMenu?: () => void; }) { + const sdk = useActiveSdk(); return (