chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:29:49 +08:00
commit 505bac6b53
1470 changed files with 457757 additions and 0 deletions
File diff suppressed because it is too large Load Diff
+64
View File
@@ -0,0 +1,64 @@
/**
* The Components section inventory: one entry per component page,
* alphabetical. Single source for the sidebar section, the index-page
* grid, and page titles/OG metadata. `preview` names the engine-rendered
* tile pair in /public/components — the index grid uses the 16:9 hero
* tiles (one representative variation per component; regenerate with
* `zig build docs-component-previews`).
*/
export type ComponentPage = {
slug: string;
name: string;
/** Preview tile stem: /components/<preview>-{light,dark}.webp */
preview: string;
/** One-line index-grid caption. */
blurb: string;
};
export const componentPages: ComponentPage[] = [
{ slug: "accordion", name: "Accordion", preview: "accordion-hero", blurb: "Disclosure surface with a model-owned open state." },
{ slug: "alert", name: "Alert", preview: "alert-hero", blurb: "Inline callouts with icon and variant color." },
{ slug: "avatar", name: "Avatar", preview: "avatar-hero", blurb: "Initials fallback and runtime-registered images." },
{ slug: "badge", name: "Badge", preview: "badge-hero", blurb: "Status labels in every variant." },
{ slug: "breadcrumb", name: "Breadcrumb", preview: "breadcrumb-hero", blurb: "Hierarchy trail with separators." },
{ slug: "bubble", name: "Bubble", preview: "bubble-hero", blurb: "Chat-message surfaces for either side." },
{ slug: "button", name: "Button", preview: "button-hero", blurb: "Variants, sizes, inline icons, and states." },
{ slug: "button-group", name: "Button Group", preview: "button-group-hero", blurb: "Attached action buttons as one segmented bar." },
{ slug: "card", name: "Card", preview: "card-hero", blurb: "The bordered, elevated surface container." },
{ slug: "chart", name: "Chart", preview: "chart-hero", blurb: "Line, bar, and band series (Zig builder)." },
{ slug: "checkbox", name: "Checkbox", preview: "checkbox-hero", blurb: "Binary choice with model-owned state." },
{ slug: "combobox", name: "Combobox", preview: "combobox-hero", blurb: "Text entry with an anchored suggestions menu." },
{ slug: "dialog", name: "Dialog", preview: "dialog-hero", blurb: "Modal surface with model-owned dismissal." },
{ slug: "drawer", name: "Drawer", preview: "drawer-hero", blurb: "Side-anchored modal surface." },
{ slug: "dropdown-menu", name: "Dropdown Menu", preview: "dropdown-menu-hero", blurb: "Anchored floating menus and menu items." },
{ slug: "icon", name: "Icon", preview: "icon-hero", blurb: "The built-in vector icon registry." },
{ slug: "input", name: "Input", preview: "input-hero", blurb: "Single-line text entry: input, text field, search field." },
{ slug: "input-group", name: "Input Group", preview: "input-group-hero", blurb: "One bordered field: textarea plus accessory actions." },
{ slug: "list", name: "List", preview: "list-hero", blurb: "Rows with icons, selection, and virtualization." },
{ slug: "markdown", name: "Markdown", preview: "markdown-hero", blurb: "GFM rendering through native widgets." },
{ slug: "pagination", name: "Pagination", preview: "pagination-hero", blurb: "Page navigation row." },
{ slug: "panel", name: "Panel", preview: "panel-hero", blurb: "The plain surface container." },
{ slug: "progress", name: "Progress", preview: "progress-hero", blurb: "Determinate progress bar." },
{ slug: "radio", name: "Radio", preview: "radio-hero", blurb: "Single choice within a radio group." },
{ slug: "resizable", name: "Resizable", preview: "resizable-hero", blurb: "Panel with an engine-managed drag handle." },
{ slug: "scroll", name: "Scroll", preview: "scroll-hero", blurb: "Scroll regions with model-observable offsets." },
{ slug: "select", name: "Select", preview: "select-hero", blurb: "Trigger plus the anchored dropdown options pattern." },
{ slug: "separator", name: "Separator", preview: "separator-hero", blurb: "Hairline rules, horizontal and vertical." },
{ slug: "sheet", name: "Sheet", preview: "sheet-hero", blurb: "Bottom-anchored modal surface." },
{ slug: "skeleton", name: "Skeleton", preview: "skeleton-hero", blurb: "Loading placeholders that sketch the content." },
{ slug: "slider", name: "Slider", preview: "slider-hero", blurb: "Continuous value control." },
{ slug: "spacer", name: "Spacer", preview: "spacer-hero", blurb: "Flexible empty space between siblings." },
{ slug: "spinner", name: "Spinner", preview: "spinner-hero", blurb: "Indeterminate progress leaf." },
{ slug: "split", name: "Split", preview: "split-hero", blurb: "Draggable two-pane splitter." },
{ slug: "status-bar", name: "Status Bar", preview: "status-bar-hero", blurb: "Window-bottom status text." },
{ slug: "stepper", name: "Stepper", preview: "stepper-hero", blurb: "Stage progress with completed/active/pending steps." },
{ slug: "switch", name: "Switch", preview: "switch-hero", blurb: "On/off switches with model-owned state." },
{ slug: "table", name: "Table", preview: "table-hero", blurb: "Rows and cells with hairline dividers." },
{ slug: "tabs", name: "Tabs", preview: "tabs-hero", blurb: "Tab strip over segmented controls." },
{ slug: "textarea", name: "Textarea", preview: "textarea-hero", blurb: "Multi-line text entry." },
{ slug: "timeline", name: "Timeline", preview: "timeline-hero", blurb: "Ledger list with indicators and connectors." },
{ slug: "toggle", name: "Toggle", preview: "toggle-hero", blurb: "Pressed-state toggles, toggle buttons, and groups." },
{ slug: "tooltip", name: "Tooltip", preview: "tooltip-hero", blurb: "The floating label above the control it annotates." },
{ slug: "tree", name: "Tree", preview: "tree-hero", blurb: "Disclosure tree with one roving focus set." },
{ slug: "virtual-list", name: "Virtual List", preview: "virtual-list-hero", blurb: "Windowed rows: the view builds only what's visible." },
];
+106
View File
@@ -0,0 +1,106 @@
import { componentPages } from "./components-pages";
export type NavItem = {
name: string;
href: string;
};
export type NavSection = {
title: string;
items: NavItem[];
};
export const navSections: NavSection[] = [
{
title: "Get Started",
items: [
{ name: "Introduction", href: "/introduction" },
{ name: "Quick Start", href: "/quick-start" },
{ name: "CLI", href: "/cli" },
{ name: "Config", href: "/app-zon" },
{ name: "Agent Skills", href: "/skills" },
],
},
{
title: "Core Concepts",
items: [
{ name: "App Model", href: "/app-model" },
{ name: "TypeScript Cores", href: "/typescript" },
{ name: "Where Packages Go", href: "/typescript/packages" },
{ name: "Native UI", href: "/native-ui" },
{ name: "State & Data Flow", href: "/state" },
{ name: "Theming", href: "/theming" },
{ name: "Building Components", href: "/building-components" },
],
},
{
// One entry per built-in component page, generated from the shared
// components-pages inventory (previews regenerate via
// `zig build docs-component-previews`).
title: "Components",
items: [
{ name: "Overview", href: "/components" },
...componentPages.map((page) => ({ name: page.name, href: `/components/${page.slug}` })),
],
},
{
title: "Native Platform",
items: [
{ name: "Windows", href: "/windows" },
{ name: "Native Surfaces", href: "/native-surfaces" },
{ name: "Menus", href: "/menus" },
{ name: "Dialogs", href: "/dialogs" },
{ name: "System Tray", href: "/tray" },
{ name: "Keyboard Shortcuts", href: "/keyboard-shortcuts" },
{ name: "Commands", href: "/commands" },
{ name: "Native Controls", href: "/native-controls" },
],
},
{
title: "Automation & Testing",
items: [
{ name: "Automation", href: "/automation" },
{ name: "Testing", href: "/testing" },
{ name: "Testing in CI", href: "/testing/ci" },
],
},
{
title: "Packaging & Distribution",
items: [
{ name: "Packaging", href: "/packaging" },
{ name: "Code Signing", href: "/packaging/signing" },
{ name: "Updates", href: "/updates" },
{ name: "Package Distribution", href: "/packages" },
],
},
{
title: "Mobile & Embedding",
items: [{ name: "Embedded App", href: "/embed" }],
},
{
title: "Web Content",
items: [
{ name: "Web Engines", href: "/web-engines" },
{ name: "Web Content", href: "/frontend" },
{ name: "Dev Server", href: "/cli/dev" },
{ name: "Multiple WebViews", href: "/webviews" },
{ name: "Bridge", href: "/bridge" },
{ name: "Builtin Commands", href: "/bridge/builtin-commands" },
],
},
{
title: "Reference",
items: [
{ name: "App & Runtime", href: "/runtime" },
{ name: "Capabilities", href: "/capabilities" },
{ name: "Security", href: "/security" },
{ name: "Platform Support", href: "/platform-support" },
{ name: "Debugging", href: "/debugging" },
{ name: "native doctor", href: "/debugging/doctor" },
{ name: "Zig 0.16 Notes", href: "/zig" },
{ name: "Extensions", href: "/extensions" },
],
},
];
export const allDocsPages: NavItem[] = navSections.flatMap((s) => s.items);
+20
View File
@@ -0,0 +1,20 @@
const REPO = "vercel-labs/native";
const REVALIDATE = 86400;
export async function getStarCount(): Promise<string> {
try {
const res = await fetch(`https://api.github.com/repos/${REPO}`, {
headers: { Accept: "application/vnd.github.v3+json" },
next: { revalidate: REVALIDATE },
});
if (!res.ok) return "";
const data = await res.json();
const count = data.stargazers_count;
if (typeof count !== "number") return "";
if (count >= 1000)
return `${(count / 1000).toFixed(count >= 10000 ? 0 : 1)}k`;
return String(count);
} catch {
return "";
}
}
+304
View File
@@ -0,0 +1,304 @@
/**
* Loader + thin typed wrapper for the live component-preview engine:
* the Native SDK canvas runtime compiled to wasm32-freestanding
* (`zig build docs-wasm-preview` → /public/wasm/component-preview.wasm).
*
* One wasm instance is shared by every preview on the page; each
* preview is an engine-side handle (its own retained runtime + scene).
* The module has zero imports — the page owns the clock and the canvas,
* and `render` reports whether the retained display list actually
* changed so an idle preview never repaints or blits.
*/
interface PreviewExports {
memory: WebAssembly.Memory;
preview_alloc(len: number): number;
preview_free(ptr: number, len: number): void;
preview_instance_bytes(): number;
preview_set_now_ms(ms: number): void;
preview_create(namePtr: number, nameLen: number, dark: number): number;
preview_destroy(handle: number): void;
preview_logical_width(handle: number): number;
preview_logical_height(handle: number): number;
preview_set_theme(handle: number, dark: number): number;
preview_set_theme_pack(handle: number, namePtr: number, nameLen: number): number;
preview_pointer(handle: number, kind: number, x: number, y: number): void;
preview_scroll(handle: number, x: number, y: number, dx: number, dy: number): void;
preview_key(
handle: number,
phase: number,
keyPtr: number,
keyLen: number,
textPtr: number,
textLen: number,
modifiers: number,
): void;
preview_text(handle: number, textPtr: number, textLen: number): void;
preview_set_focused(handle: number, focused: number): void;
preview_text_input_active(handle: number): number;
preview_cursor(handle: number): number;
preview_frame(handle: number): void;
preview_pixel_width(handle: number, scale: number): number;
preview_pixel_height(handle: number, scale: number): number;
preview_pixel_byte_len(handle: number, scale: number): number;
preview_render(
handle: number,
scale: number,
pixelsPtr: number,
pixelsLen: number,
scratchPtr: number,
scratchLen: number,
): number;
}
/**
* The engine's built-in theme packs by their manifest-facing names.
* A pack is a complete token register (palette, control tables, type
* scale) that composes with the light/dark scheme axis, so any pack ×
* scheme combination is a live re-theme, never a scene rebuild from JS.
*/
export const themePacks = ["house", "geist"] as const;
export type ThemePack = (typeof themePacks)[number];
export const PointerKind = {
down: 0,
up: 1,
move: 2,
drag: 3,
cancel: 4,
} as const;
const encoder = new TextEncoder();
export class PreviewEngine {
constructor(private exports: PreviewExports) {}
create(scene: string, dark: boolean): LivePreview | null {
const e = this.exports;
const bytes = encoder.encode(scene);
const ptr = e.preview_alloc(bytes.length);
if (!ptr) return null;
new Uint8Array(e.memory.buffer).set(bytes, ptr);
const handle = e.preview_create(ptr, bytes.length, dark ? 1 : 0);
e.preview_free(ptr, bytes.length);
if (!handle) return null;
return new LivePreview(e, handle);
}
}
export class LivePreview {
readonly logicalWidth: number;
readonly logicalHeight: number;
private pixelsPtr = 0;
private scratchPtr = 0;
private bufferLen = 0;
private imageData: ImageData | null = null;
private destroyed = false;
constructor(
private exports: PreviewExports,
private handle: number,
) {
this.logicalWidth = exports.preview_logical_width(handle);
this.logicalHeight = exports.preview_logical_height(handle);
}
setNow(ms: number): void {
this.exports.preview_set_now_ms(ms);
}
pointer(kind: number, x: number, y: number): void {
if (this.destroyed) return;
this.exports.preview_pointer(this.handle, kind, x, y);
}
scroll(x: number, y: number, dx: number, dy: number): void {
if (this.destroyed) return;
this.exports.preview_scroll(this.handle, x, y, dx, dy);
}
key(phase: 0 | 1, key: string, text: string, modifiers: number): void {
if (this.destroyed) return;
const e = this.exports;
const keyBytes = encoder.encode(key);
const textBytes = encoder.encode(text);
const len = keyBytes.length + textBytes.length;
const ptr = len > 0 ? e.preview_alloc(len) : 0;
if (len > 0 && !ptr) return;
if (ptr) {
const mem = new Uint8Array(e.memory.buffer);
mem.set(keyBytes, ptr);
mem.set(textBytes, ptr + keyBytes.length);
}
e.preview_key(this.handle, phase, ptr, keyBytes.length, ptr + keyBytes.length, textBytes.length, modifiers);
if (ptr) e.preview_free(ptr, len);
}
text(value: string): void {
if (this.destroyed) return;
const e = this.exports;
const bytes = encoder.encode(value);
if (bytes.length === 0) return;
const ptr = e.preview_alloc(bytes.length);
if (!ptr) return;
new Uint8Array(e.memory.buffer).set(bytes, ptr);
e.preview_text(this.handle, ptr, bytes.length);
e.preview_free(ptr, bytes.length);
}
textInputActive(): boolean {
if (this.destroyed) return false;
return this.exports.preview_text_input_active(this.handle) !== 0;
}
/**
* The engine's cursor for the pointer's current hover target, mapped
* to a CSS cursor keyword. The engine follows the native register:
* arrow over controls (buttons, toggles, rows — the platform
* convention), pointer ONLY over true hyperlinks, I-beam over text
* entry, col-resize over split dividers and resizable edges. The
* mirror is a straight enum-to-keyword map — it must never re-add
* `pointer` for controls on the CSS side.
*/
cursor(): string {
if (this.destroyed) return "default";
switch (this.exports.preview_cursor(this.handle)) {
case 1:
return "pointer";
case 2:
return "text";
case 3:
return "col-resize";
default:
return "default";
}
}
/**
* Mirror the canvas element's DOM focus into the engine view: a blur
* drops the focus ring, caret, and caret-blink animation (parking the
* render loop); a focus restores them for the retained focused widget.
*/
setFocused(focused: boolean): void {
if (this.destroyed) return;
this.exports.preview_set_focused(this.handle, focused ? 1 : 0);
}
setTheme(dark: boolean): void {
if (this.destroyed) return;
this.exports.preview_set_theme(this.handle, dark ? 1 : 0);
}
/**
* Switch the theme pack by name — the pack axis of the same in-place
* re-theme `setTheme` performs on the scheme axis. The engine refuses
* unknown names, so a stale page can never half-apply a pack.
*/
setThemePack(pack: ThemePack): void {
if (this.destroyed) return;
const e = this.exports;
const bytes = encoder.encode(pack);
const ptr = e.preview_alloc(bytes.length);
if (!ptr) return;
new Uint8Array(e.memory.buffer).set(bytes, ptr);
e.preview_set_theme_pack(this.handle, ptr, bytes.length);
e.preview_free(ptr, bytes.length);
}
/** Step engine-owned frame animations (scroll momentum). */
frame(): void {
if (this.destroyed) return;
this.exports.preview_frame(this.handle);
}
pixelWidth(scale: number): number {
return this.destroyed ? 0 : this.exports.preview_pixel_width(this.handle, scale);
}
pixelHeight(scale: number): number {
return this.destroyed ? 0 : this.exports.preview_pixel_height(this.handle, scale);
}
/**
* Render at `scale` device pixels per logical unit. Returns the fresh
* ImageData when the engine repainted, or null when the retained scene
* is unchanged (skip the blit) or on error.
*/
render(scale: number): ImageData | null {
if (this.destroyed) return null;
const e = this.exports;
const byteLen = e.preview_pixel_byte_len(this.handle, scale);
if (!byteLen) return null;
if (byteLen !== this.bufferLen) {
this.releaseBuffers();
this.pixelsPtr = e.preview_alloc(byteLen);
this.scratchPtr = e.preview_alloc(byteLen);
if (!this.pixelsPtr || !this.scratchPtr) {
this.releaseBuffers();
return null;
}
this.bufferLen = byteLen;
this.imageData = null;
}
const status = e.preview_render(this.handle, scale, this.pixelsPtr, byteLen, this.scratchPtr, byteLen);
if (status !== 1) return null;
const width = e.preview_pixel_width(this.handle, scale);
const height = e.preview_pixel_height(this.handle, scale);
if (!width || !height) return null;
if (!this.imageData || this.imageData.width !== width || this.imageData.height !== height) {
this.imageData = new ImageData(width, height);
}
// Copy out of wasm memory: the buffer may move on memory growth, so
// never hand long-lived views to the canvas.
this.imageData.data.set(new Uint8ClampedArray(e.memory.buffer, this.pixelsPtr, byteLen));
return this.imageData;
}
private releaseBuffers(): void {
const e = this.exports;
if (this.pixelsPtr) e.preview_free(this.pixelsPtr, this.bufferLen);
if (this.scratchPtr) e.preview_free(this.scratchPtr, this.bufferLen);
this.pixelsPtr = 0;
this.scratchPtr = 0;
this.bufferLen = 0;
this.imageData = null;
}
destroy(): void {
if (this.destroyed) return;
this.releaseBuffers();
this.exports.preview_destroy(this.handle);
this.destroyed = true;
}
}
let enginePromise: Promise<PreviewEngine | null> | null = null;
/**
* Fetch + instantiate the shared engine once. Resolves null when wasm
* is unavailable (the static webp layer simply stays).
*/
export function loadPreviewEngine(): Promise<PreviewEngine | null> {
if (!enginePromise) {
enginePromise = instantiate().catch(() => null);
}
return enginePromise;
}
async function instantiate(): Promise<PreviewEngine | null> {
if (typeof WebAssembly === "undefined") return null;
const url = "/wasm/component-preview.wasm";
let instance: WebAssembly.Instance;
try {
const streamed = await WebAssembly.instantiateStreaming(fetch(url), {});
instance = streamed.instance;
} catch {
// Older servers without the application/wasm MIME type.
const response = await fetch(url);
if (!response.ok) return null;
const bytes = await response.arrayBuffer();
const result = await WebAssembly.instantiate(bytes, {});
instance = result.instance;
}
return new PreviewEngine(instance.exports as unknown as PreviewExports);
}
+140
View File
@@ -0,0 +1,140 @@
import vocab from "./component-vocab.json";
import { componentPages } from "./components-pages";
/**
* Turn a docs page.mdx source into clean plain markdown for "Copy Page"
* and the /md/<slug> route. The .mdx source is the truth; this strips the
* MDX-only parts (imports, JSX components) and replaces the data-driven
* components with the same content they render:
*
* - `<AttrTable attrs={[...]} element="x" />` becomes a markdown table
* using the same component-vocab.json lookup the component uses.
* - `<ComponentIndexGrid />` becomes the component index as a list.
* - `<IconGallery />` becomes the icon-name list from the vocabulary.
* - `<ComponentPreview ... />` (an engine-rendered image) is dropped.
* - `<CodeToggle>` wrappers are unwrapped: the fenced samples inside are
* plain markdown already, so only the tags drop.
* - Filenamed fences (```ts:src/core.ts) become a labeled `path`: line
* above a plain ```ts fence.
*
* Inline HTML (tables, definition lists) passes through untouched —
* it is valid markdown as written.
*/
export function mdxToCleanMarkdown(raw: string): string {
const lines = raw.split("\n");
const out: string[] = [];
let jsxBlock: string[] | null = null;
let inFence = false;
for (const line of lines) {
const trimmed = line.trim();
// Fenced code passes through verbatim: TypeScript samples start lines
// with `import`/`export`, which only MDX-level lines may strip. The one
// rewrite is a filenamed opener (```ts:src/core.ts — the site renders
// the path as a header row): copied markdown gets the path as a labeled
// line above a plain fence, so any renderer highlights the real
// language instead of choking on the colon.
if (jsxBlock === null && trimmed.startsWith("```")) {
if (!inFence) {
const meta = trimmed.match(/^```([^\s:`]+):(\S+)$/);
if (meta) {
out.push(`\`${meta[2]}\`:`);
out.push("");
out.push("```" + meta[1]);
inFence = true;
continue;
}
}
inFence = !inFence;
out.push(line);
continue;
}
if (inFence) {
out.push(line);
continue;
}
if (jsxBlock === null && (trimmed.startsWith("export ") || trimmed.startsWith("import "))) {
continue;
}
// CodeToggle is the one paired-tag component: its children are ordinary
// fenced code blocks, valid markdown as written, so drop only the tags.
if (jsxBlock === null && (trimmed === "<CodeToggle>" || trimmed === "</CodeToggle>")) {
continue;
}
// A capitalized JSX component; every other docs component is self-closing.
if (jsxBlock === null && /^<[A-Z]/.test(trimmed)) {
jsxBlock = [line];
} else if (jsxBlock !== null) {
jsxBlock.push(line);
} else {
out.push(line);
continue;
}
if (trimmed.endsWith("/>")) {
const replacement = renderJsxComponent(jsxBlock.join("\n"));
if (replacement) out.push(replacement);
jsxBlock = null;
}
}
return out
.join("\n")
.replace(/\n{3,}/g, "\n\n")
.trim();
}
type Doc = { name: string; doc: string };
function renderJsxComponent(block: string): string | null {
if (block.startsWith("<AttrTable")) return renderAttrTable(block);
if (block.startsWith("<ComponentIndexGrid")) return renderComponentIndex();
if (block.startsWith("<IconGallery")) return renderIconList();
// ComponentPreview and anything unknown: an image or purely visual
// element with no markdown equivalent.
return null;
}
/** The same lookup AttrTable does: scoped table first, then shared tables. */
function lookupAttr(name: string, element?: string): Doc | undefined {
if (element) {
const table = (vocab.scoped as Record<string, Doc[]>)[element];
const hit = table?.find((doc) => doc.name === name);
if (hit) return hit;
}
return (
(vocab.attributes as Doc[]).find((doc) => doc.name === name) ??
(vocab.events as Doc[]).find((doc) => doc.name === name)
);
}
function renderAttrTable(block: string): string {
const element = block.match(/element="([^"]+)"/)?.[1];
const attrsSource = block.match(/attrs=\{\[([\s\S]*?)\]\}/)?.[1] ?? "";
const attrs = [...attrsSource.matchAll(/"([^"]+)"/g)].map((m) => m[1]!);
const rows = attrs.map((name) => {
const doc = lookupAttr(name, element);
return `| \`${name}\` | ${doc ? escapeCell(doc.doc) : ""} |`;
});
return ["| Attribute | Description |", "| --- | --- |", ...rows].join("\n");
}
function renderComponentIndex(): string {
return componentPages
.map((page) => `- [${page.name}](/components/${page.slug}) — ${page.blurb}`)
.join("\n");
}
function renderIconList(): string {
const icons = vocab.icons as string[];
return `Built-in icon names: ${icons.map((name) => `\`${name}\``).join(", ")}.`;
}
function escapeCell(text: string): string {
return text.replace(/\|/g, "\\|").replace(/\n/g, " ");
}
+39
View File
@@ -0,0 +1,39 @@
import type { Metadata } from "next";
import { PAGE_TITLES } from "./page-titles";
const DESCRIPTION =
"Toolkit for building native desktop apps: declarative markup views, a predictable message-based state model, and its own engine rendering into real OS windows — no browser or WebView in the binary.";
export function pageMetadata(slug: string): Metadata {
const title = PAGE_TITLES[slug];
if (!title) return {};
const displayTitle = title.replace(/\n/g, " ");
const fullTitle = `${displayTitle} | Native SDK`;
const ogImageUrl = slug ? `/og/${slug}` : "/og";
return {
title: displayTitle,
openGraph: {
type: "website",
locale: "en_US",
siteName: "Native SDK",
title: fullTitle,
description: DESCRIPTION,
images: [
{
url: ogImageUrl,
width: 1200,
height: 630,
alt: `${displayTitle} - Native SDK`,
},
],
},
twitter: {
card: "summary_large_image",
title: fullTitle,
description: DESCRIPTION,
images: [ogImageUrl],
},
};
}
+55
View File
@@ -0,0 +1,55 @@
import { componentPages } from "./components-pages";
export const PAGE_TITLES: Record<string, string> = {
components: "Components",
// One title per component page, from the shared inventory.
...Object.fromEntries(componentPages.map((page) => [`components/${page.slug}`, page.name])),
"": "The Complete Toolkit\nfor Native Apps",
introduction: "Introduction",
"quick-start": "Quick Start",
"app-model": "App Model",
typescript: "TypeScript Cores",
"typescript/packages": "Where Packages Go",
"native-ui": "Native UI",
state: "State & Data Flow",
theming: "Theming",
"building-components": "Building Components",
runtime: "App & Runtime",
frontend: "Frontend Projects",
"native-surfaces": "Native Surfaces",
windows: "Windows",
webviews: "Multiple WebViews",
"keyboard-shortcuts": "Keyboard Shortcuts",
commands: "Commands",
bridge: "Bridge",
"bridge/builtin-commands": "Builtin Commands",
dialogs: "Dialogs",
menus: "Menus",
"native-controls": "Native Controls",
"built-in-components": "Built-in Components",
capabilities: "Capabilities",
"platform-support": "Platform Support",
tray: "System Tray",
security: "Security",
cli: "CLI",
"cli/dev": "Dev Server",
skills: "Agent Skills",
packaging: "Packaging",
"packaging/signing": "Code Signing",
updates: "Updates",
"app-zon": "Config",
debugging: "Debugging",
"debugging/doctor": "native doctor",
automation: "Automation",
testing: "Testing",
"testing/ci": "Testing in CI",
extensions: "Extensions",
embed: "Embedded App",
"web-engines": "Web Engines",
packages: "Package Distribution",
zig: "Zig 0.16 Notes",
};
export function getPageTitle(slug: string): string | null {
return slug in PAGE_TITLES ? PAGE_TITLES[slug]! : null;
}
+52
View File
@@ -0,0 +1,52 @@
import { allDocsPages } from "./docs-navigation";
import { readFile } from "node:fs/promises";
import path from "node:path";
export type IndexEntry = {
title: string;
href: string;
content: string;
};
let cached: IndexEntry[] | null = null;
export async function getSearchIndex(): Promise<IndexEntry[]> {
if (cached) return cached;
// The homepage lives in the site header, not the docs nav, so index it explicitly.
const pages = [{ name: "Home", href: "/" }, ...allDocsPages];
const entries: IndexEntry[] = await Promise.all(
pages.map(async (item) => ({
title: item.name,
href: item.href,
content: await pageContent(item.href, item.name),
})),
);
cached = entries;
return entries;
}
async function pageContent(href: string, fallback: string): Promise<string> {
// The homepage is a TSX landing page, not MDX; index a hand-written summary.
if (href === "/") {
return "Toolkit for building native desktop apps. Cross-platform native UI: declarative Native markup views (.native), design tokens, TypeScript or Zig app cores, a predictable message-based state loop, hot reload, typed effects, AI agent skills and evals, automation, WebView coexistence on macOS, Linux, Windows, iOS, and Android.";
}
const relative = path.join(href.slice(1), "page.mdx");
const filePath = path.join(process.cwd(), "src", "app", relative);
try {
const source = await readFile(filePath, "utf8");
return stripMdx(source);
} catch {
return fallback;
}
}
function stripMdx(source: string): string {
return source
.replace(/```[\s\S]*?```/g, " ")
.replace(/<[^>]+>/g, " ")
.replace(/[#*_`[\](){}>-]/g, " ")
.replace(/\s+/g, " ")
.trim();
}
+22
View File
@@ -0,0 +1,22 @@
/**
* The single source of truth for the project's public name and every
* name-bearing string on the site. Rename day touches this file only —
* all copy elsewhere is written name-neutral and pulls from here.
*/
export const siteName = "Native SDK";
/** The npm package that carries the CLI. */
export const npmCli = "@native-sdk/cli";
/** The public repository. */
export const githubUrl = "https://github.com/vercel-labs/native";
/** The canonical site origin (used for metadata + Open Graph). */
export const siteUrl = "https://native-sdk.dev";
/** The one-line tagline used in titles and Open Graph text. */
export const tagline = "The Complete Toolkit for Native Apps";
/** The one-sentence description used in metadata. */
export const description =
"Native SDK is the complete toolkit for building beautiful native desktop applications: declarative markup, a predictable message-based state model, a modern component library, and its own native renderer — no browser, no WebView, no compromise.";
+6
View File
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}