From a9e2d9c5732ba97f01c9a7059e88a58178fb8b5c Mon Sep 17 00:00:00 2001 From: Sil Date: Mon, 6 Jul 2026 20:48:35 +0200 Subject: [PATCH 01/12] Remove request-time server rendering --- CONTEXT.md | 24 +-- CONTRIBUTING.md | 2 +- apps/docs/src/components/layouts/base.tsx | 1 - apps/docs/src/pages/docs/content.mdx | 2 +- apps/docs/src/pages/docs/deployment.mdx | 28 --- apps/docs/src/pages/docs/index.mdx | 2 +- apps/docs/src/pages/docs/installation.mdx | 3 +- apps/docs/src/pages/docs/page-routing.mdx | 8 +- .../src/pages/docs/rendering-and-assets.mdx | 27 +-- apps/docs/src/pages/docs/tsx-pages.mdx | 29 +--- apps/docs/src/pages/rendering-test/server.tsx | 20 --- docs/children-include-handoff.md | 1 - packages/sitex/CHANGELOG.md | 4 +- packages/sitex/package.json | 1 - packages/sitex/src/index.ts | 1 - packages/sitex/src/nitro/renderer.ts | 7 - packages/sitex/src/router/runtime.ts | 39 +---- packages/sitex/src/types/nitro-vite.d.ts | 5 - packages/sitex/src/types/virtual.d.ts | 5 - packages/sitex/src/vite/plugin.ts | 162 +----------------- packages/sitex/vite.config.ts | 3 - pnpm-lock.yaml | 87 ---------- pnpm-workspace.yaml | 1 - 23 files changed, 30 insertions(+), 432 deletions(-) delete mode 100644 apps/docs/src/pages/docs/deployment.mdx delete mode 100644 apps/docs/src/pages/rendering-test/server.tsx delete mode 100644 packages/sitex/src/nitro/renderer.ts delete mode 100644 packages/sitex/src/types/nitro-vite.d.ts diff --git a/CONTEXT.md b/CONTEXT.md index 016365a..6e1b9f3 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -5,8 +5,8 @@ Sitex is an experimental framework for building static sites from React route mo ## Language **Static route**: -A URL that Sitex can render to static HTML ahead of serving a request. A static route may be authored directly as a local route file today, and may later be generated from data without becoming an SSR route. -_Avoid_: SSR route, request route +A URL that Sitex renders to static HTML during build. A static route may be authored directly as a local route file today, and may later be generated from data. +_Avoid_: request route **Local route**: A static route represented by one concrete route file in the app. This is the current v0 routing target because it keeps routing explicit and simple. @@ -22,7 +22,7 @@ _Avoid_: route, entry **Generated static route**: A static route produced from one route module plus one or more data entries. This is a possible future routing shape for external or local data sources, but not the current v0 default. -_Avoid_: dynamic route, SSR route +_Avoid_: dynamic route **Generated static route module**: A future route module that explicitly exports `definePages(...)` to produce multiple static routes. Generated static route modules should be signaled by their API shape, not by catch-all filename syntax. @@ -117,25 +117,17 @@ The imported React component that carries a `client:*` directive. An island root _Avoid_: intrinsic island, inline island **Rendering mode**: -The way Sitex decides where a page or component is rendered. Current page modes are static by default and explicit request-time rendering with `export const render = "server"`. Current island modes are static plus client rendering with `client:load`, `client:visible`, `client:idle`, or `client:media`, and browser-only rendering with `client:only`. +The way Sitex decides where a component is rendered. Pages are static. Island modes are static plus client rendering with `client:load`, `client:visible`, `client:idle`, or `client:media`, and browser-only rendering with `client:only`. _Avoid_: hydration mode **Static rendering**: The default rendering mode. Static rendering produces HTML ahead of serving requests and does not attach browser interactivity. -_Avoid_: server rendering +_Avoid_: hydration **Client rendering**: Browser rendering used for interactivity. `client:load`, `client:visible`, `client:idle`, and `client:media` combine static rendering with client rendering, while `client:only` uses client rendering only. _Avoid_: hydration -**Server rendering**: -Request-time page rendering on a server. Sitex supports this only through explicit route opt-in with `export const render = "server"`. -_Avoid_: static rendering - -**Server directive**: -A possible future directive for request-time server rendering behavior. Server directives are reserved conceptually but are not part of the current Sitex API. -_Avoid_: current API - **Client-loaded island**: An island marked with `client:load`. A client-loaded island is statically rendered first and then client-rendered for interactivity. _Avoid_: browser-only island @@ -183,7 +175,7 @@ _Avoid_: complete framework ## Flagged Ambiguities **Hydration naming**: -The code currently uses `hydration` names for the island runtime, but the preferred product language is rendering modes: static rendering, client rendering, and future server rendering. Do not change the code during the grilling session; revisit this in the later cleanup/refactor. +The code currently uses `hydration` names for the island runtime, but the preferred product language is rendering modes: static rendering and client rendering. Do not change the code during the grilling session; revisit this in the later cleanup/refactor. **Document helpers**: The current `HeadContent` and `Scripts` helpers leak build asset concerns into the app-owned document shell. Cleanup should remove them and replace them with framework-owned build asset injection. @@ -203,10 +195,6 @@ _Avoid_: `children:include` ## Example Dialogue -Developer: "Should this service page be a server route?" - -Domain expert: "No. It should be a static route unless it actually needs request-time data." - Developer: "Where do page metadata and scripts belong?" Domain expert: "In the app-owned document shell. Sitex can provide primitives, but the app's layouts own the actual document structure." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a8d54fb..4b8a71a 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -22,5 +22,5 @@ pnpm ready - Keep the public API small: the Vite plugin, TypeScript config, and client directives. - Prefer static routes and explicit TSX files. -- Do not add SSR, generated routes, content collections, or new client directives without first documenting the decision. +- Document the decision before adding generated routes, content collections, rendering modes, or new client directives. - Preserve the reference app as a simple content-site example. diff --git a/apps/docs/src/components/layouts/base.tsx b/apps/docs/src/components/layouts/base.tsx index c76584c..a562296 100644 --- a/apps/docs/src/components/layouts/base.tsx +++ b/apps/docs/src/components/layouts/base.tsx @@ -22,7 +22,6 @@ const docsNavigationGroups = [ { href: "/docs", label: "Introduction" }, { href: "/docs/installation", label: "Installation" }, { href: "/docs/folder-structure", label: "Folder structure" }, - { href: "/docs/deployment", label: "Deployment" }, ], }, { diff --git a/apps/docs/src/pages/docs/content.mdx b/apps/docs/src/pages/docs/content.mdx index 7a9d43a..543c74f 100644 --- a/apps/docs/src/pages/docs/content.mdx +++ b/apps/docs/src/pages/docs/content.mdx @@ -110,4 +110,4 @@ export const data = { Use strings, numbers, booleans, arrays, plain objects, and `null`. Do not use JSX, functions, class instances, dates, maps, sets, or cyclic values. -Because route modules run during discovery, top-level page code must be build-safe. Avoid browser-only globals, network side effects, and request-time state. +Because route modules run during discovery, top-level page code must be build-safe. Avoid browser-only globals and network side effects. diff --git a/apps/docs/src/pages/docs/deployment.mdx b/apps/docs/src/pages/docs/deployment.mdx deleted file mode 100644 index 2d59585..0000000 --- a/apps/docs/src/pages/docs/deployment.mdx +++ /dev/null @@ -1,28 +0,0 @@ ---- -layout: "doc" -title: "Deployment" -description: "Deploy static SiteX sites and sites with server routes." -order: 10 ---- - -## Overview - -Static-only SiteX sites deploy like normal Vite sites. - -If any route exports `render = "server"`, deploy to a host that can run server routes. Netlify, Vercel, and Cloudflare are detected automatically. - -## Static Sites - -```bash -pnpm build -``` - -Publish the generated output directory for your app. - -## Server Routes - -Server routes run at request time and are not written as static HTML files. - -```tsx -export const render = "server" -``` diff --git a/apps/docs/src/pages/docs/index.mdx b/apps/docs/src/pages/docs/index.mdx index 427d669..9b9a5ce 100644 --- a/apps/docs/src/pages/docs/index.mdx +++ b/apps/docs/src/pages/docs/index.mdx @@ -13,7 +13,7 @@ Routes are local TSX files. Sitex maps them to URLs, keeps routing explicit and Imported components can opt into client rendering with directives like `client:load`, `client:visible`, and `client:idle`. Read more in [Island rendering](/docs/island-rendering). -Static routes, server routes, route-level CSS, and island assets are handled by Vite's module graph. Read more in [Rendering and assets](/docs/rendering-and-assets). +Static routes, route-level CSS, and island assets are handled by Vite's module graph. Read more in [Rendering and assets](/docs/rendering-and-assets). ## Use cases diff --git a/apps/docs/src/pages/docs/installation.mdx b/apps/docs/src/pages/docs/installation.mdx index c038504..97c85d1 100644 --- a/apps/docs/src/pages/docs/installation.mdx +++ b/apps/docs/src/pages/docs/installation.mdx @@ -111,8 +111,7 @@ vp preview ## Next Steps 1. Read [Routing](/docs/page-routing) for the route types. -2. Read [TSX pages](/docs/tsx-pages) for static, dynamic, and server routes. +2. Read [TSX pages](/docs/tsx-pages) for static and dynamic routes. 3. Read [MDX pages](/docs/mdx) for prose pages with layouts. 4. Read [Pages API](/docs/content) for indexes, cards, and navigation from route metadata. 5. Read [Component rendering](/docs/island-rendering) for browser interactivity. -6. Read [Deployment](/docs/deployment) before deploying server routes. diff --git a/apps/docs/src/pages/docs/page-routing.mdx b/apps/docs/src/pages/docs/page-routing.mdx index 59e1725..3ac2581 100644 --- a/apps/docs/src/pages/docs/page-routing.mdx +++ b/apps/docs/src/pages/docs/page-routing.mdx @@ -11,7 +11,7 @@ TSX and MDX files in `src/pages` become routes. Routes are static by default. ## Route Types -Use TSX for component-heavy pages, dynamic routes, and server routes. Use MDX for prose-heavy pages. +Use TSX for component-heavy pages and dynamic routes. Use MDX for prose-heavy pages. 1. [TSX pages](/docs/tsx-pages) export a React component from `.tsx`. 2. [MDX pages](/docs/mdx) use Markdown, frontmatter, and a layout. @@ -36,8 +36,4 @@ A bracket segment becomes a route parameter. Dynamic static routes use TSX and e Read [TSX pages](/docs/tsx-pages) for a full dynamic route example. -## Server Routes - -Export `render = "server"` from a TSX route when a page needs request-time data. - -Read [Page rendering](/docs/rendering-and-assets) for how static and server routes are rendered, and [Deployment](/docs/deployment) before deploying server routes. +Read [Page rendering](/docs/rendering-and-assets) for how routes are rendered and how assets are included. diff --git a/apps/docs/src/pages/docs/rendering-and-assets.mdx b/apps/docs/src/pages/docs/rendering-and-assets.mdx index ee2259d..a693f1a 100644 --- a/apps/docs/src/pages/docs/rendering-and-assets.mdx +++ b/apps/docs/src/pages/docs/rendering-and-assets.mdx @@ -1,13 +1,13 @@ --- layout: "doc" title: "Page rendering" -description: "How SiteX renders static pages, dynamic pages, server pages, and route CSS." +description: "How SiteX renders static pages, dynamic pages, and route CSS." order: 8 --- ## Overview -Sitex renders pages to HTML at build time unless a route opts into server rendering. +Sitex renders pages to HTML at build time. ## Static Routes @@ -41,29 +41,6 @@ export default function Page({ params }) { } ``` -## Server Routes - -Export `render = "server"` when a route needs the incoming request. - -```tsx -export const render = "server" - -export default function RequestPage({ request }) { - const url = new URL(request.url) - - return ( - - -

Server route

-

{url.pathname}

- - - ) -} -``` - -Server routes are not written as static HTML files. See [Deployment](/docs/deployment). - ## CSS Assets Import CSS from routes, layouts, or components. SiteX includes the CSS needed by each rendered route. diff --git a/apps/docs/src/pages/docs/tsx-pages.mdx b/apps/docs/src/pages/docs/tsx-pages.mdx index f43c1f0..98d1462 100644 --- a/apps/docs/src/pages/docs/tsx-pages.mdx +++ b/apps/docs/src/pages/docs/tsx-pages.mdx @@ -1,13 +1,13 @@ --- layout: "doc" title: "TSX pages" -description: "Create static, dynamic, and server-rendered pages with React components." +description: "Create static and dynamic pages with React components." order: 4 --- ## Overview -Use TSX pages for component-heavy pages, dynamic paths, and request-time server rendering. +Use TSX pages for component-heavy pages and dynamic paths. ```tsx export default function AboutPage() { @@ -66,31 +66,6 @@ export default function BlogPost({ `src/pages/blog/[slug].tsx` can render `/blog/hello-world` and `/blog/second-post`. -## Server Pages - -Export `render = "server"` when a page needs request-time data. - -```tsx -import type { PageContext } from "@fulldotdev/sitex" - -export const render = "server" - -export default function PreviewPage({ request }: PageContext) { - const url = request ? new URL(request.url) : undefined - - return ( - - -

Preview

-

{url?.searchParams.get("token")}

- - - ) -} -``` - -Keep pages static unless they need the request. - ## Page Data Static TSX pages can export `data` for indexes, cards, and navigation. diff --git a/apps/docs/src/pages/rendering-test/server.tsx b/apps/docs/src/pages/rendering-test/server.tsx deleted file mode 100644 index 926b4b3..0000000 --- a/apps/docs/src/pages/rendering-test/server.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import type { PageContext } from "@fulldotdev/sitex" - -export const render = "server" - -export default async function Page({ request, url }: PageContext) { - return ( - - - SiteX server rendering test - - -
-

Server rendering test

-

server route rendered

-

{url.pathname}

-
- - - ) -} diff --git a/docs/children-include-handoff.md b/docs/children-include-handoff.md index 97e7e27..49aac8c 100644 --- a/docs/children-include-handoff.md +++ b/docs/children-include-handoff.md @@ -259,6 +259,5 @@ client:only client:visible client:idle client:media -server:defer children:include ``` diff --git a/packages/sitex/CHANGELOG.md b/packages/sitex/CHANGELOG.md index 00ee6fb..8a6448a 100644 --- a/packages/sitex/CHANGELOG.md +++ b/packages/sitex/CHANGELOG.md @@ -6,11 +6,11 @@ - Add MDX pages with layout-based rendering, page data from MDX frontmatter, and configurable MDX components. - Add server-rendered TSX pages through `export const render = "server"`, while keeping static routes as the default build output. + Keep TSX and MDX routes static, with explicit dynamic static paths for generated HTML output. Improve island rendering with `client:load`, `client:only`, `client:visible`, `client:idle`, and `client:media`. - Refresh the documentation for the 0.3 page model, deployment behavior, component rendering, and project structure. + Refresh the documentation for the 0.3 page model, component rendering, and project structure. ## 0.2.1 diff --git a/packages/sitex/package.json b/packages/sitex/package.json index de15c80..4586346 100644 --- a/packages/sitex/package.json +++ b/packages/sitex/package.json @@ -45,7 +45,6 @@ }, "dependencies": { "fast-glob": "^3.3.3", - "nitro": "catalog:", "quicktype-core": "^23.2.6", "satteri": "catalog:", "yaml": "catalog:" diff --git a/packages/sitex/src/index.ts b/packages/sitex/src/index.ts index 61d57ce..ca55b27 100644 --- a/packages/sitex/src/index.ts +++ b/packages/sitex/src/index.ts @@ -5,7 +5,6 @@ export type { PageContext, Route, RouteParams, - RouteRenderMode, StaticPath, StaticPaths, } from "./router/runtime.ts" diff --git a/packages/sitex/src/nitro/renderer.ts b/packages/sitex/src/nitro/renderer.ts deleted file mode 100644 index 8f8542a..0000000 --- a/packages/sitex/src/nitro/renderer.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { renderServerResponse } from "virtual:sitex-render" - -export default async function renderer({ req }: { req: Request; url: URL }) { - const response = await renderServerResponse(req) - - return response ?? new Response("Not found", { status: 404 }) -} diff --git a/packages/sitex/src/router/runtime.ts b/packages/sitex/src/router/runtime.ts index b4e6f5b..9ea4e91 100644 --- a/packages/sitex/src/router/runtime.ts +++ b/packages/sitex/src/router/runtime.ts @@ -9,7 +9,6 @@ export type JsonValue = | { [key: string]: JsonValue } export type RouteParams = Record -export type RouteRenderMode = "static" | "server" export type StaticPath = { params: RouteParams @@ -23,7 +22,6 @@ export type StaticPaths = export type PageContext = { params: RouteParams props: Props - request?: Request url: URL } @@ -51,7 +49,6 @@ export type Route = { params: RouteParams path: string props: Props - render: RouteRenderMode score: number } @@ -77,33 +74,15 @@ export async function readPageRoutes( throw new Error(`Page module "${file}" must default export a component.`) } - if (pageModule.render !== undefined && pageModule.render !== "server") { - const renderMode = JSON.stringify(pageModule.render) - + if (pageModule.render !== undefined) { throw new Error( - `Page module "${file}" has unsupported render mode ${renderMode}. Only "server" is supported.` + `Page module "${file}" exports render, but Sitex routes are static. Remove the render export.` ) } - const render: RouteRenderMode = - pageModule.render === "server" ? "server" : "static" const pattern = pageFileToRoutePattern(file) const paramNames = readParamNames(pattern) - if (render === "server") { - return [ - createRoute({ - file, - layout: component as PageLayout, - paramNames, - params: {}, - path: pattern, - props: undefined as unknown, - render, - }), - ] - } - if (paramNames.length === 0) { if (pageModule.paths !== undefined) { throw new Error( @@ -119,15 +98,12 @@ export async function readPageRoutes( params: {}, path: pattern, props: undefined as unknown, - render, }), ] } if (pageModule.paths === undefined) { - throw new Error( - `Dynamic static page module "${file}" must export paths or render = "server".` - ) + throw new Error(`Dynamic page module "${file}" must export paths.`) } const paths = @@ -149,7 +125,6 @@ export async function readPageRoutes( params: path.params, path: fillRoutePattern(pattern, path.params), props: path.props as unknown, - render, }) }) } @@ -175,17 +150,13 @@ export function matchRoute( export function createPageContext( route: Route, - params: RouteParams, - request?: Request + params: RouteParams ): PageContext { - const url = request - ? new URL(request.url) - : new URL(route.path, "https://sitex.local") + const url = new URL(route.path, "https://sitex.local") return { params, props: route.props, - request, url, } } diff --git a/packages/sitex/src/types/nitro-vite.d.ts b/packages/sitex/src/types/nitro-vite.d.ts deleted file mode 100644 index 1fa8019..0000000 --- a/packages/sitex/src/types/nitro-vite.d.ts +++ /dev/null @@ -1,5 +0,0 @@ -declare module "nitro/vite" { - import type { Plugin } from "vite-plus" - - export function nitro(pluginConfig?: Record): Plugin[] -} diff --git a/packages/sitex/src/types/virtual.d.ts b/packages/sitex/src/types/virtual.d.ts index 7416de9..86e12fe 100644 --- a/packages/sitex/src/types/virtual.d.ts +++ b/packages/sitex/src/types/virtual.d.ts @@ -20,7 +20,6 @@ declare module "virtual:sitex-render" { assetTags?: string islandClientPreamble?: string islandClientSrc?: string - request?: Request } type RenderResult = { @@ -39,10 +38,6 @@ declare module "virtual:sitex-render" { params: Record, options?: RenderOptions ): Promise - export function renderServerResponse( - request: Request, - options?: RenderOptions - ): Promise } declare module "sitex:pages" { diff --git a/packages/sitex/src/vite/plugin.ts b/packages/sitex/src/vite/plugin.ts index 4b2d1bd..37e835e 100644 --- a/packages/sitex/src/vite/plugin.ts +++ b/packages/sitex/src/vite/plugin.ts @@ -30,7 +30,6 @@ import path from "node:path" import { fileURLToPath } from "node:url" import fg from "fast-glob" -import { nitro } from "nitro/vite" import { defineHastPlugin, mdxToJs } from "satteri" import { parse as parseYaml } from "yaml" @@ -118,20 +117,6 @@ export function sitex(options: SitexOptions = {}): PluginOption[] { } const plugins: PluginOption[] = [sitexPlugin(resolvedOptions)] - if ( - process.env.SITEX_INTERNAL_RENDER !== "1" && - hasServerPagesSync(process.cwd()) - ) { - plugins.push( - ...(nitro({ - renderer: { - handler: packageFile("nitro/renderer", "ts"), - }, - serverDir: ".", - }) as PluginOption[]) - ) - } - plugins.push(sitexBuildInputPlugin()) return plugins @@ -225,11 +210,7 @@ function sitexPlugin(options: ResolvedSitexOptions): Plugin { async buildStart() { hydration.clear() - if ( - config.command === "build" && - !cleanedBuildOutput && - process.env.SITEX_INTERNAL_RENDER !== "1" - ) { + if (config.command === "build" && !cleanedBuildOutput) { cleanedBuildOutput = true await rm(path.join(root, "dist"), { recursive: true, force: true }) } @@ -277,7 +258,7 @@ function sitexPlugin(options: ResolvedSitexOptions): Plugin { } if (id === resolvedVirtualRenderId) { - return createJavaScriptModule(createVirtualRenderCode(root)) + return createJavaScriptModule(createVirtualRenderCode()) } if (id === resolvedVirtualPagesId) { @@ -324,11 +305,10 @@ function sitexPlugin(options: ResolvedSitexOptions): Plugin { } const url = req.url?.split("?")[0] ?? "/" - const request = createWebRequest(req) try { const { render } = await importServerModule(server, virtualRenderId) - const initialHtml = await render(url, { request }) + const initialHtml = await render(url) if (!initialHtml) { next() @@ -344,7 +324,6 @@ function sitexPlugin(options: ResolvedSitexOptions): Plugin { assetTags: renderStylesheetTags(stylesheetHrefs), islandClientPreamble: renderReactRefreshFallbackScript(), islandClientSrc: devServerFileUrl("hydration/client", "tsx"), - request, }) if (!html) { @@ -477,9 +456,6 @@ async function writeStaticHtml(root: string, options: ResolvedSitexOptions) { ) const cssAssetMap = createCssAssetMap(root, manifest) - const previousInternalRender = process.env.SITEX_INTERNAL_RENDER - process.env.SITEX_INTERNAL_RENDER = "1" - const server = await createServer({ configFile: path.join(root, "vite.config.ts"), server: { middlewareMode: true }, @@ -497,8 +473,6 @@ async function writeStaticHtml(root: string, options: ResolvedSitexOptions) { const routes = await getRoutes() for (const route of routes) { - if (route.render === "server") continue - const html = await renderMatchedRoute(route, route.params) if (!html) { @@ -528,12 +502,6 @@ async function writeStaticHtml(root: string, options: ResolvedSitexOptions) { } } finally { await server.close() - - if (previousInternalRender === undefined) { - delete process.env.SITEX_INTERNAL_RENDER - } else { - process.env.SITEX_INTERNAL_RENDER = previousInternalRender - } } } @@ -564,24 +532,6 @@ function collectStyleFilesSync(root: string) { }) } -function hasServerPagesSync(root: string) { - return collectServerRouteFilesSync(root).length > 0 -} - -function collectServerRouteFilesSync(root: string) { - const files = fg.sync("src/pages/**/*.tsx", { - cwd: root, - ignore: ["src/pages/examples/**"], - onlyFiles: true, - }) - - return files.filter((file) => { - const code = readFileSync(path.join(root, file), "utf8") - - return /\bexport\s+const\s+render\s*=\s*["']server["']/.test(code) - }) -} - async function collectPageDataRoutes( root: string, options: ResolvedSitexOptions @@ -1092,68 +1042,12 @@ async function createPagesTypesCode(pages: PageData[]) { ].join("\n") } -function createVirtualRenderCode(root: string) { - const serverRouteFiles = collectServerRouteFilesSync(root).map( - (file) => `/${file}` - ) - +function createVirtualRenderCode() { return [ `import { prerender } from "react-dom/static"`, - `import { renderToString } from "react-dom/server"`, `import { getRoutes } from ${JSON.stringify(virtualRoutesId)}`, `import { createPageContext, matchRoute, readPageRoutes } from ${JSON.stringify(packageFile("router/runtime", "ts"))}`, `export { getRoutes }`, - `const serverRouteModules = import.meta.glob(["/src/pages/**/*.tsx", "/src/pages/**/*.mdx"])`, - `const serverRouteFiles = ${JSON.stringify(serverRouteFiles)}`, - `function isRouteFile(file) {`, - ` return !file.includes("/src/pages/examples/")`, - `}`, - `function normalizeRoutePath(path) {`, - ` if (!path || path === "/") return "/"`, - ` return "/" + path.replace(/^\\/+|\\/+$/g, "")`, - `}`, - `function routeFileToPattern(file) {`, - ` const route = file.replace(/^\\/+/, "").replace(/^src\\/pages/, "").replace(/\\/index\\.(tsx|mdx)$/, "/").replace(/\\.(tsx|mdx)$/, "")`, - ` return normalizeRoutePath(route || "/")`, - `}`, - `function routeScore(path) {`, - ` return normalizeRoutePath(path).split("/").reduce((score, segment) => score + (!segment ? 0 : segment.startsWith("[") ? 1 : 10), 0)`, - `}`, - `function matchPathPattern(pattern, path) {`, - ` const patternSegments = normalizeRoutePath(pattern).split("/")`, - ` const pathSegments = normalizeRoutePath(path).split("/")`, - ` if (patternSegments.length !== pathSegments.length) return undefined`, - ` const params = {}`, - ` for (let index = 0; index < patternSegments.length; index++) {`, - ` const patternSegment = patternSegments[index]`, - ` const pathSegment = pathSegments[index]`, - ` const paramMatch = patternSegment.match(/^\\[([^\\]]+)\\]$/)`, - ` if (paramMatch) {`, - ` params[paramMatch[1]] = decodeURIComponent(pathSegment)`, - ` continue`, - ` }`, - ` if (patternSegment !== pathSegment) return undefined`, - ` }`, - ` return params`, - `}`, - `async function matchServerRoute(path) {`, - ` const candidates = []`, - ` for (const file of serverRouteFiles) {`, - ` if (!isRouteFile(file)) continue`, - ` const pattern = routeFileToPattern(file)`, - ` const params = matchPathPattern(pattern, path)`, - ` if (params) candidates.push({ file, params, pattern, score: routeScore(pattern) })`, - ` }`, - ` candidates.sort((a, b) => b.score - a.score || a.pattern.localeCompare(b.pattern))`, - ` const candidate = candidates[0]`, - ` if (!candidate) return undefined`, - ` const routeFile = candidate.file.replace(/^\\/+/, "")`, - ` const loadRoute = serverRouteModules[candidate.file]`, - ` if (!loadRoute) return undefined`, - ` const routes = await readPageRoutes(await loadRoute(), routeFile)`, - ` const match = matchRoute(routes, path)`, - ` return match ? { params: { ...candidate.params, ...match.params }, route: match.route } : undefined`, - `}`, `function replaceLast(value, search, replacement) {`, ` const index = value.lastIndexOf(search)`, ` if (index === -1) return value`, @@ -1183,15 +1077,9 @@ function createVirtualRenderCode(root: string) { ` return { html, params, route }`, `}`, `async function renderRouteHtml(route, params, options = {}) {`, - ` const request = route.render === "server" ? options.request : undefined`, - ` const context = createPageContext(route, params, request)`, + ` const context = createPageContext(route, params)`, ` const node = await route.layout(context)`, - ` let body`, - ` if (route.render === "server") {`, - ` body = renderToString(node)`, - ` } else {`, - ` body = await new Response((await prerender(node)).prelude).text()`, - ` }`, + ` const body = await new Response((await prerender(node)).prelude).text()`, ` const html = /^\\s*" + body`, ` return injectRouteHtmlAssets(html, options)`, `}`, @@ -1205,42 +1093,9 @@ function createVirtualRenderCode(root: string) { `export async function renderMatchedRoute(route, params, options = {}) {`, ` return renderRouteHtml(route, params, options)`, `}`, - `export async function renderServerResponse(request, options = {}) {`, - ` const url = new URL(request.url)`, - ` const match = await matchServerRoute(url.pathname)`, - ` if (!match) return undefined`, - ` const html = await renderRouteHtml(match.route, match.params, { ...options, request })`, - ` return new Response(html, { headers: { "content-type": "text/html; charset=utf-8" } })`, - `}`, ].join("\n") } -function createWebRequest(req: Connect.IncomingMessage) { - const protocolHeader = req.headers["x-forwarded-proto"] - const protocol = Array.isArray(protocolHeader) - ? (protocolHeader[0] ?? "http") - : (protocolHeader ?? "http") - const host = req.headers.host ?? "localhost" - const url = new URL(req.url ?? "/", `${protocol}://${host}`) - const headers = new Headers() - - for (const [key, value] of Object.entries(req.headers)) { - if (value === undefined) continue - - if (Array.isArray(value)) { - for (const item of value) headers.append(key, item) - continue - } - - headers.set(key, value) - } - - return new Request(url, { - headers, - method: req.method, - }) -} - function routePathToHtmlFile( root: string, routePath: string, @@ -1388,10 +1243,7 @@ function routePathToPublicPath(path: string, trailingSlash: boolean) { } async function findBuildPublicRoot(root: string) { - for (const publicRoot of [ - path.join(root, "dist"), - path.join(root, ".output/public"), - ]) { + for (const publicRoot of [path.join(root, "dist")]) { try { await access(path.join(publicRoot, ".vite/manifest.json")) return publicRoot diff --git a/packages/sitex/vite.config.ts b/packages/sitex/vite.config.ts index 267529e..ddba42d 100644 --- a/packages/sitex/vite.config.ts +++ b/packages/sitex/vite.config.ts @@ -7,7 +7,6 @@ export default defineConfig({ "vite/plugin": "src/vite/plugin.ts", "hydration/client": "src/hydration/client.tsx", "hydration/server": "src/hydration/server.tsx", - "nitro/renderer": "src/nitro/renderer.ts", "router/runtime": "src/router/runtime.ts", }, outDir: "dist", @@ -20,8 +19,6 @@ export default defineConfig({ /^node:/, /^virtual:/, "fast-glob", - "nitro", - "nitro/vite", "quicktype-core", "react", "react-dom", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8bb4891..62fff6c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,9 +9,6 @@ catalogs: '@base-ui/react': specifier: ^1.5.0 version: 1.5.0 - nitro: - specifier: 3.0.260603-beta - version: 3.0.260603-beta satteri: specifier: ^0.8.0 version: 0.8.0 @@ -115,9 +112,6 @@ importers: fast-glob: specifier: ^3.3.3 version: 3.3.3 - nitro: - specifier: 'catalog:' - version: 3.0.260603-beta(@voidzero-dev/vite-plus-core@0.1.24(@types/node@24.12.4)(jiti@2.7.0)(typescript@5.9.3)(yaml@2.9.0))(dotenv@17.4.2)(jiti@2.7.0) quicktype-core: specifier: ^23.2.6 version: 23.2.6 @@ -2373,37 +2367,6 @@ packages: nf3@0.3.17: resolution: {integrity: sha512-N9zEWySuJFw+gR0lhS5863YsvNeudOdqRyFvNb+jMXbeTJOdrjDqkCpDginIZfUm0LzT1t1nCRiDeqQm/8kirQ==} - nitro@3.0.260603-beta: - resolution: {integrity: sha512-ffaSHK00a7YDlDizoEHwcxPwpQpdBRRA8k42ymTsRnfl3ipGeKgv4gnPr6DgmCNTo4tYVPK3bHBEv1gNhWpo/A==} - engines: {node: ^20.19.0 || >=22.12.0} - hasBin: true - peerDependencies: - '@vercel/queue': ^0.2.0 - dotenv: '*' - giget: '*' - jiti: ^2.7.0 - rollup: ^4.60.4 - vite: ^7 || ^8 - xml2js: ^0.6.2 - zephyr-agent: ^0.2.0 - peerDependenciesMeta: - '@vercel/queue': - optional: true - dotenv: - optional: true - giget: - optional: true - jiti: - optional: true - rollup: - optional: true - vite: - optional: true - xml2js: - optional: true - zephyr-agent: - optional: true - node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} @@ -5286,56 +5249,6 @@ snapshots: nf3@0.3.17: {} - nitro@3.0.260603-beta(@voidzero-dev/vite-plus-core@0.1.24(@types/node@24.12.4)(jiti@2.7.0)(typescript@5.9.3)(yaml@2.9.0))(dotenv@17.4.2)(jiti@2.7.0): - dependencies: - consola: 3.4.2 - crossws: 0.4.5(srvx@0.11.16) - db0: 0.3.4 - env-runner: 0.1.9 - h3: 2.0.1-rc.22(crossws@0.4.5(srvx@0.11.16)) - hookable: 6.1.1 - nf3: 0.3.17 - ocache: 0.1.4 - ofetch: 2.0.0-alpha.3 - ohash: 2.0.11 - rolldown: 1.0.3 - srvx: 0.11.16 - unenv: 2.0.0-rc.24 - unstorage: 2.0.0-alpha.7(db0@0.3.4)(ofetch@2.0.0-alpha.3) - optionalDependencies: - dotenv: 17.4.2 - jiti: 2.7.0 - vite: '@voidzero-dev/vite-plus-core@0.1.24(@types/node@24.12.4)(jiti@2.7.0)(typescript@5.9.3)(yaml@2.9.0)' - transitivePeerDependencies: - - '@azure/app-configuration' - - '@azure/cosmos' - - '@azure/data-tables' - - '@azure/identity' - - '@azure/keyvault-secrets' - - '@azure/storage-blob' - - '@capacitor/preferences' - - '@deno/kv' - - '@electric-sql/pglite' - - '@libsql/client' - - '@netlify/blobs' - - '@netlify/runtime' - - '@planetscale/database' - - '@upstash/redis' - - '@vercel/blob' - - '@vercel/functions' - - '@vercel/kv' - - aws4fetch - - better-sqlite3 - - chokidar - - drizzle-orm - - idb-keyval - - ioredis - - lru-cache - - miniflare - - mongodb - - mysql2 - - sqlite3 - - uploadthing node-domexception@1.0.0: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index d54f16a..a35918e 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -16,7 +16,6 @@ catalog: "@ianvs/prettier-plugin-sort-imports": ^4.7.1 prettier-plugin-tailwindcss: ^0.8.0 html-minifier-next: ^6.2.8 - nitro: 3.0.260603-beta satteri: ^0.8.0 yaml: ^2.9.0 From ecb3779c3a9c89803ddf86def07de2a684a6c223 Mon Sep 17 00:00:00 2001 From: Sil Date: Wed, 8 Jul 2026 17:52:58 +0200 Subject: [PATCH 02/12] Rework rendering pipeline, split UI registry, clean up repo - Render static HTML in-process via an SSR build environment (no second Vite server) and replace regex HTML handling with parse5 - Globals: index.yaml is the root locale; {locale}.yaml files serve /{locale} routes; locale name configurable via site.locale - Move UI components to packages/ui as a shadcn registry served from the docs site (/r); sitex npm package exports types only - Docs: Framework/UI sidebar split, /docs/ui page, remove unused components and internal test pages - Only merge layout mdxComponents when the layout exports it - Remove SitexDocument no-op, legacy shims, dead exports and aliases - Update all dependencies to latest (typescript 6, satteri 0.9, @vitejs/plugin-react 6, vite-plus 0.2) - Restore brief CONTRIBUTING.md and CODE_OF_CONDUCT.md Co-Authored-By: Claude Fable 5 --- .changeset/opinionated-mdx-rebuild.md | 25 + .gitignore | 3 + AGENTS.md | 8 - CONTEXT.md | 212 - CONTRIBUTING.md | 10 +- apps/docs/components.json | 2 +- apps/docs/package.json | 43 +- apps/docs/public/favicon.svg | 7 - apps/docs/src/assets/.gitkeep | 1 + apps/docs/src/components/blocks/sidebar-1.tsx | 213 +- apps/docs/src/components/layouts/base.tsx | 135 - apps/docs/src/components/layouts/doc.tsx | 36 - apps/docs/src/components/layouts/home.tsx | 19 - apps/docs/src/components/ui/breadcrumb.tsx | 122 - apps/docs/src/components/ui/dialog.tsx | 154 - apps/docs/src/components/ui/dropdown-menu.tsx | 270 -- apps/docs/src/components/ui/input-group.tsx | 158 - apps/docs/src/components/ui/label.tsx | 20 - apps/docs/src/components/ui/layout.tsx | 135 + apps/docs/src/components/ui/textarea.tsx | 18 - apps/docs/src/components/ui/toggle.tsx | 45 - apps/docs/src/components/ui/typography.tsx | 115 +- apps/docs/src/globals/index.yaml | 49 + .../src/{styles/globals.css => index.css} | 81 +- apps/docs/src/layouts/base.tsx | 51 + apps/docs/src/layouts/doc.tsx | 64 +- apps/docs/src/layouts/home.tsx | 55 + apps/docs/src/layouts/mdx-test.tsx | 14 - apps/docs/src/lib/globals.ts | 40 + apps/docs/src/pages/docs/content.mdx | 71 +- apps/docs/src/pages/docs/folder-structure.mdx | 28 +- apps/docs/src/pages/docs/index.mdx | 33 +- apps/docs/src/pages/docs/installation.mdx | 144 +- apps/docs/src/pages/docs/island-rendering.mdx | 27 +- apps/docs/src/pages/docs/layouts.mdx | 155 + apps/docs/src/pages/docs/mdx-components.mdx | 8 +- apps/docs/src/pages/docs/mdx.mdx | 101 - apps/docs/src/pages/docs/page-routing.mdx | 39 - apps/docs/src/pages/docs/pages.mdx | 126 + .../src/pages/docs/rendering-and-assets.mdx | 59 +- apps/docs/src/pages/docs/seo.mdx | 134 + apps/docs/src/pages/docs/tsx-pages.mdx | 84 - apps/docs/src/pages/docs/ui.mdx | 58 + apps/docs/src/pages/index.mdx | 18 + apps/docs/src/pages/index.tsx | 55 - .../pages/rendering-test/dynamic/[slug].tsx | 29 - apps/docs/src/pages/rendering-test/mdx.mdx | 6 - apps/docs/src/pages/rendering-test/static.tsx | 15 - apps/docs/tsconfig.json | 9 +- apps/docs/vite.config.ts | 4 +- docs/children-include-handoff.md | 263 -- netlify.toml | 19 +- package.json | 8 +- packages/sitex/CHANGELOG.md | 2 +- packages/sitex/README.md | 87 +- packages/sitex/package.json | 26 +- packages/sitex/src/hydration/compiler.ts | 2 - packages/sitex/src/hydration/protocol.ts | 9 - packages/sitex/src/hydration/registry.ts | 10 - packages/sitex/src/index.ts | 10 +- packages/sitex/src/render/html.ts | 133 + packages/sitex/src/render/render.tsx | 218 + packages/sitex/src/router/frontmatter.ts | 197 + packages/sitex/src/router/runtime.ts | 356 +- packages/sitex/src/site/config.ts | 108 + packages/sitex/src/types/virtual.d.ts | 67 +- packages/sitex/src/vite/plugin.ts | 1360 +++--- .../sitex/test/hydration-compiler.test.mjs | 115 - packages/sitex/vite.config.ts | 3 +- packages/ui/components.json | 19 + packages/ui/package.json | 29 + packages/ui/registry.json | 133 + packages/ui/src/components/ui/banner.tsx | 112 + packages/ui/src/components/ui/gradient.tsx | 49 + packages/ui/src/components/ui/header.tsx | 60 + packages/ui/src/components/ui/layout.tsx | 135 + packages/ui/src/components/ui/marquee.tsx | 87 + packages/ui/src/components/ui/price.tsx | 176 + packages/ui/src/components/ui/rating.tsx | 47 + packages/ui/src/components/ui/section.tsx | 53 + .../ui/src/components/ui/theme-toggle.tsx | 126 + packages/ui/src/components/ui/tile.tsx | 126 + packages/ui/src/components/ui/toc.tsx | 87 + packages/ui/src/components/ui/typography.tsx | 313 ++ packages/ui/src/lib/utils.ts | 6 + packages/ui/tsconfig.json | 15 + pnpm-lock.yaml | 3663 ++++++++--------- pnpm-workspace.yaml | 35 +- 88 files changed, 6333 insertions(+), 5239 deletions(-) create mode 100644 .changeset/opinionated-mdx-rebuild.md delete mode 100644 AGENTS.md delete mode 100644 CONTEXT.md delete mode 100644 apps/docs/public/favicon.svg create mode 100644 apps/docs/src/assets/.gitkeep delete mode 100644 apps/docs/src/components/layouts/base.tsx delete mode 100644 apps/docs/src/components/layouts/doc.tsx delete mode 100644 apps/docs/src/components/layouts/home.tsx delete mode 100644 apps/docs/src/components/ui/breadcrumb.tsx delete mode 100644 apps/docs/src/components/ui/dialog.tsx delete mode 100644 apps/docs/src/components/ui/dropdown-menu.tsx delete mode 100644 apps/docs/src/components/ui/input-group.tsx delete mode 100644 apps/docs/src/components/ui/label.tsx create mode 100644 apps/docs/src/components/ui/layout.tsx delete mode 100644 apps/docs/src/components/ui/textarea.tsx delete mode 100644 apps/docs/src/components/ui/toggle.tsx create mode 100644 apps/docs/src/globals/index.yaml rename apps/docs/src/{styles/globals.css => index.css} (85%) create mode 100644 apps/docs/src/layouts/base.tsx create mode 100644 apps/docs/src/layouts/home.tsx delete mode 100644 apps/docs/src/layouts/mdx-test.tsx create mode 100644 apps/docs/src/lib/globals.ts create mode 100644 apps/docs/src/pages/docs/layouts.mdx delete mode 100644 apps/docs/src/pages/docs/mdx.mdx delete mode 100644 apps/docs/src/pages/docs/page-routing.mdx create mode 100644 apps/docs/src/pages/docs/pages.mdx create mode 100644 apps/docs/src/pages/docs/seo.mdx delete mode 100644 apps/docs/src/pages/docs/tsx-pages.mdx create mode 100644 apps/docs/src/pages/docs/ui.mdx create mode 100644 apps/docs/src/pages/index.mdx delete mode 100644 apps/docs/src/pages/index.tsx delete mode 100644 apps/docs/src/pages/rendering-test/dynamic/[slug].tsx delete mode 100644 apps/docs/src/pages/rendering-test/mdx.mdx delete mode 100644 apps/docs/src/pages/rendering-test/static.tsx delete mode 100644 docs/children-include-handoff.md create mode 100644 packages/sitex/src/render/html.ts create mode 100644 packages/sitex/src/render/render.tsx create mode 100644 packages/sitex/src/router/frontmatter.ts create mode 100644 packages/sitex/src/site/config.ts delete mode 100644 packages/sitex/test/hydration-compiler.test.mjs create mode 100644 packages/ui/components.json create mode 100644 packages/ui/package.json create mode 100644 packages/ui/registry.json create mode 100644 packages/ui/src/components/ui/banner.tsx create mode 100644 packages/ui/src/components/ui/gradient.tsx create mode 100644 packages/ui/src/components/ui/header.tsx create mode 100644 packages/ui/src/components/ui/layout.tsx create mode 100644 packages/ui/src/components/ui/marquee.tsx create mode 100644 packages/ui/src/components/ui/price.tsx create mode 100644 packages/ui/src/components/ui/rating.tsx create mode 100644 packages/ui/src/components/ui/section.tsx create mode 100644 packages/ui/src/components/ui/theme-toggle.tsx create mode 100644 packages/ui/src/components/ui/tile.tsx create mode 100644 packages/ui/src/components/ui/toc.tsx create mode 100644 packages/ui/src/components/ui/typography.tsx create mode 100644 packages/ui/src/lib/utils.ts create mode 100644 packages/ui/tsconfig.json diff --git a/.changeset/opinionated-mdx-rebuild.md b/.changeset/opinionated-mdx-rebuild.md new file mode 100644 index 0000000..630f796 --- /dev/null +++ b/.changeset/opinionated-mdx-rebuild.md @@ -0,0 +1,25 @@ +--- +"@fulldotdev/sitex": minor +--- + +Rebuild Sitex around MDX-only pages and a framework-owned document head. + +Breaking changes: + +- Pages are MDX files only. TSX files in `src/pages` fail the build with a migration error; move page markup into layouts and blocks. Dynamic `[param]` routes and the `paths` export are removed. +- The `site` config is required: `sitex({ site: { name, url } })`. Optional fields: `description`, `titleTemplate`, `locale`, `image`, `favicon`, `twitter`, `organization`. +- Frontmatter follows a validated schema: `layout`, `title`, and `description` are required. `type` (`webpage` | `article` | `faq`) drives JSON-LD structured data; `image`, `canonical`, and `index` are optional overrides. Articles require `publishedAt`; FAQ pages require `questions`. +- Apps do not provide `index.html`. Layouts render the full document (``/``/``) and Sitex parses the rendered page to add missing head defaults (charset, viewport, canonical, og:url, robots, favicon, generator) and hoist JSON-LD into the head. +- `src/styles/global.css` is required and automatically imported as the global stylesheet entry. Tailwind is not bundled by Sitex; apps that use Tailwind install `tailwindcss` and `@tailwindcss/vite` themselves. +- `MarkdownLayoutProps` is renamed to `LayoutProps` and now includes the page schema fields. `PageContext`, `StaticPath`, and `StaticPaths` are removed. +- `sitex:pages` has a stable, statically declared `Page` type; generated `.sitex/pages.d.ts` files are no longer written (legacy ones are cleaned up automatically). +- The `src/pages/examples/**` exemption is removed; everything under `src/pages` is a page. +- The main entry no longer exports UI components (`Layout`, `Section`, `Typography`, `Price`, and friends). They moved to the Sitex shadcn registry; install them with `shadcn add` from `https://sitex.full.dev/r/{name}.json`. `@fulldotdev/sitex` now exports types only, plus the `/plugin` and `/island` entries. +- Globals moved to `src/globals/index.yaml` as the root locale file. The root locale name comes from the new `site.locale` option (default `"en"`); additional files like `nl.yaml` define locale variants served under `/nl` route prefixes and are exposed through the `locales` export of `sitex:globals`. A single named file like `en.yaml` still works as the root locale. +- Static HTML is rendered in-process during `vp build` through a dedicated SSR build environment; Sitex no longer boots a second Vite server after the client build. + +New: + +- `vp build` writes `sitemap.xml` (respects `index: false` and `canonical` overrides, `lastmod` from `updatedAt`/`publishedAt`) and a default `robots.txt` when the app ships none. +- Path-alias imports (like `@/components/alert`) now resolve inside MDX pages on newer Vite resolvers. +- Frontmatter validation errors are targeted per field, and MDX pages are typechecked against their layout props via `.sitex/typecheck`. diff --git a/.gitignore b/.gitignore index 0e4ecde..910882e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ coverage/ # Local Netlify folder .netlify + +# generated shadcn registry +apps/docs/public/r/ diff --git a/AGENTS.md b/AGENTS.md deleted file mode 100644 index 4557048..0000000 --- a/AGENTS.md +++ /dev/null @@ -1,8 +0,0 @@ -# AGENTS.md - -- SiteX is a Vite-based React framework for static content sites. -- The repo follows the Vite+ monorepo shape: docs app in `apps/docs`, package in `packages/sitex`. -- Docs routes live in `apps/docs/src/pages` and render to static HTML by default. -- Client interactivity uses imported islands with `client:load`, `client:only`, `client:visible`, `client:idle`, or `client:media`. -- Keep routes thin: routes choose layouts, layouts compose blocks, blocks use UI. -- Do not add new rendering modes, generated routes, content collections, or new directives casually. diff --git a/CONTEXT.md b/CONTEXT.md deleted file mode 100644 index 6e1b9f3..0000000 --- a/CONTEXT.md +++ /dev/null @@ -1,212 +0,0 @@ -# Sitex - -Sitex is an experimental framework for building static sites from React route modules with narrowly scoped client interactivity. - -## Language - -**Static route**: -A URL that Sitex renders to static HTML during build. A static route may be authored directly as a local route file today, and may later be generated from data. -_Avoid_: request route - -**Local route**: -A static route represented by one concrete route file in the app. This is the current v0 routing target because it keeps routing explicit and simple. -_Avoid_: dynamic route - -**Route module**: -A file discovered by Sitex routing. A local route module default-exports a page component; a future generated static route module may default-export `definePages(...)`. -_Avoid_: page - -**Page component**: -The React component exported by a local route module to render one static route. -_Avoid_: route, entry - -**Generated static route**: -A static route produced from one route module plus one or more data entries. This is a possible future routing shape for external or local data sources, but not the current v0 default. -_Avoid_: dynamic route - -**Generated static route module**: -A future route module that explicitly exports `definePages(...)` to produce multiple static routes. Generated static route modules should be signaled by their API shape, not by catch-all filename syntax. -_Avoid_: catch-all route, `[...page]` - -**Route entry**: -One data item used by a future generated static route module to produce one static route. -_Avoid_: page, route - -**App-provided data**: -Data loaded or declared by the app and passed into Sitex route APIs. Sitex core should not require a built-in content collection model. -_Avoid_: Sitex content collection - -**Recommended app structure**: -An app-owned organization where route modules stay thin, layouts compose page chrome, blocks define reusable page sections, and UI primitives hold low-level design-system components. Sitex recommends this structure but does not require or interpret it. -_Avoid_: framework layout system - -**Reference app**: -An app that both documents Sitex and proves the recommended app structure against real static routes and islands. The docs app is the current reference app. -_Avoid_: component showcase - -**Repository shape**: -The repo follows Vite+'s monorepo shape: `packages/sitex` contains the framework package and `apps/docs` contains the docs/reference app. -_Avoid_: mixing package and app concerns in the repository root - -**Framework import**: -Published apps should import Sitex through package-style public imports such as `@fulldotdev/sitex/plugin`. The in-repo docs app may import package source directly when dogfooding unpublished changes. -_Avoid_: exposing framework internals as app-facing public APIs - -**Hidden island runtime**: -The React runtime code Sitex uses under the hood to mark, load, hydrate, and client-render islands. App authors should not need to import this directly. -_Avoid_: React helpers - -**Public surface**: -The current public surface should be the Sitex Vite plugin and JSX client directives. Other runtime pieces should stay hidden behind compiler/plugin behavior. -_Avoid_: direct island imports, document helper imports - -**Router export**: -Route rendering internals are framework-owned virtual module code. Apps should not import route rendering helpers directly. -_Avoid_: public router helpers - -**TypeScript config export**: -`@fulldotdev/sitex/tsconfig` may remain as a convenience for framework-required compiler settings and JSX directive types. It should not become a broad app preference bundle. -_Avoid_: app style config - -**Directive validation**: -TypeScript may broadly allow `client:*` attributes, but the compiler is responsible for rejecting invalid directive placement such as HTML elements or local components. -_Avoid_: type-only enforcement - -**Package build**: -Sitex is published as `@fulldotdev/sitex` from `packages/sitex`. Public app-facing imports should use exported package entrypoints, while the in-repo docs app may import package source directly when dogfooding unpublished changes. -_Avoid_: app imports from unexported internals - -**Production build**: -The public production build command should be `vp build`. Any custom Sitex build script is experiment scaffolding; cleanup should keep static route rendering and asset emission inside the Sitex Vite plugin. -_Avoid_: separate Sitex build command - -**Development server**: -The public development command should be `vp dev`. Sitex development behavior should come from the Vite plugin rather than a custom dev server command. -_Avoid_: separate Sitex dev command - -**Document shell**: -The app-owned HTML structure for a page, including ``, ``, and ``. Sitex supplies small document primitives, but the app decides the shell through its own layouts. -_Avoid_: framework document, global app root - -**Root document shell**: -The ordinary TSX document markup returned by each route render through the app's root layout. Sitex should not require Sitex-specific document components or validate more than asset injection needs. -_Avoid_: page fragment - -**Document metadata**: -Plain TSX rendered inside the app-owned document shell, such as `` and `<meta>` tags. Sitex should not require helper components for ordinary metadata. -_Avoid_: metadata API - -**Build asset injection**: -Framework-owned insertion of production asset tags such as stylesheet and island client scripts. Sitex should not require app-placed `HeadContent` or `Scripts` helper components for this. -_Avoid_: document helper components - -**Style entry**: -An app-owned CSS import that Vite includes in the module graph. Sitex should support normal Vite CSS imports wherever the app chooses to place them. -_Avoid_: required global stylesheet component - -**CSS API**: -Sitex should not expose a CSS-specific API. Styling should flow through normal Vite CSS imports and Sitex should ensure those assets appear in static output. -_Avoid_: stylesheet helper, styles config - -**Island**: -An imported React component that opts into browser interactivity inside otherwise static HTML. -_Avoid_: app root, hydrated page - -**Island root**: -The imported React component that carries a `client:*` directive. An island root cannot be an HTML element or a component declared locally inside the same route module. -_Avoid_: intrinsic island, inline island - -**Rendering mode**: -The way Sitex decides where a component is rendered. Pages are static. Island modes are static plus client rendering with `client:load`, `client:visible`, `client:idle`, or `client:media`, and browser-only rendering with `client:only`. -_Avoid_: hydration mode - -**Static rendering**: -The default rendering mode. Static rendering produces HTML ahead of serving requests and does not attach browser interactivity. -_Avoid_: hydration - -**Client rendering**: -Browser rendering used for interactivity. `client:load`, `client:visible`, `client:idle`, and `client:media` combine static rendering with client rendering, while `client:only` uses client rendering only. -_Avoid_: hydration - -**Client-loaded island**: -An island marked with `client:load`. A client-loaded island is statically rendered first and then client-rendered for interactivity. -_Avoid_: browser-only island - -**Lazy island**: -An island marked with `client:visible`, `client:idle`, or `client:media`. A lazy island is statically rendered first and then client-rendered after the chosen browser condition is met. -_Avoid_: separate route, server island - -**Static children**: -Children passed through an island boundary as static slot HTML. Static children stay visible but do not become live React children in the parent island's client tree. -_Avoid_: included children, live React children - -**Static slot model**: -The Astro-like model for island children. Children are rendered as static slot HTML, and any nested `client:*` components inside them are independent islands rather than part of the parent island's live React tree. -_Avoid_: live subtree hydration - -**Independent island**: -An island that client-renders separately from any surrounding island. Static children may contain independent islands, but those islands do not share a React tree, context, or state with the surrounding island. -_Avoid_: included island, subtree island - -**Island props**: -Data passed from static rendering into an island root. Island props must be JSON-serializable data; behavior should live inside the island root component. -_Avoid_: function props, React element props - -**Prop serialization**: -Sitex v0 should use a simple JSON-only serialization boundary for island props. Astro-like support for `Date`, `Map`, `Set`, `BigInt`, and similar values is a possible later enhancement, not cleanup scope. -_Avoid_: rich serializer - -**Invalid island prop**: -A value that cannot safely cross the static-to-client boundary, such as a function, symbol, React element, cyclic object, or ambiguous dropped value. Invalid island props should cause a hard framework error. -_Avoid_: stringified function, silently dropped prop - -**Prop serialization error**: -A targeted Sitex error raised when an island prop cannot be represented by the v0 JSON-only serializer. -_Avoid_: raw `JSON.stringify` failure - -**Browser-only island**: -An island marked with `client:only`. A browser-only island has no initial static render; it is invisible until the browser renders it. -_Avoid_: hydrated island, fallback island - -**Simplicity principle**: -Sitex should keep the current experiment extremely small and explicit, even when that postpones useful framework conveniences. -_Avoid_: complete framework - -## Flagged Ambiguities - -**Hydration naming**: -The code currently uses `hydration` names for the island runtime, but the preferred product language is rendering modes: static rendering and client rendering. Do not change the code during the grilling session; revisit this in the later cleanup/refactor. - -**Document helpers**: -The current `HeadContent` and `Scripts` helpers leak build asset concerns into the app-owned document shell. Cleanup should remove them and replace them with framework-owned build asset injection. - -**Document validation**: -Sitex should not validate document structure beyond what it needs for asset injection. Missing insertion points should produce targeted errors only when injection cannot proceed. - -**Nested island transform**: -Static children should be allowed to contain independent islands. The current compiler may miss literal nested `client:*` directives inside the same transformed JSX subtree; revisit this in the later cleanup/refactor. - -**Subtree island API**: -The earlier `children:include` experiment made a parent island and its children one React tree. It may never be needed because shared client state can be modeled by creating a larger island root component. - -**Shared client tree**: -A set of interactive components that need shared React state or context. A shared client tree should be composed inside one island root component rather than through a subtree hydration directive. -_Avoid_: `children:include` - -## Example Dialogue - -Developer: "Where do page metadata and scripts belong?" - -Domain expert: "In the app-owned document shell. Sitex can provide primitives, but the app's layouts own the actual document structure." - -Developer: "This sidebar needs search and theme toggling. Is the whole page an island?" - -Domain expert: "No. The sidebar can be an island, while the page body remains static children." - -Developer: "Should `client:only` include fallback children?" - -Domain expert: "No. A browser-only island has no initial static render; it appears when the browser renders it." - -Developer: "Filters and results need shared React state. Should I use `children:include`?" - -Domain expert: "No. Make a `Dashboard` island root that renders the provider, filters, and results internally." diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4b8a71a..f9e389d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -SiteX is experimental. Keep changes small, explicit, and aligned with the current scope. +Sitex is experimental. Keep changes small, explicit, and aligned with the current scope. ## Local setup @@ -20,7 +20,7 @@ pnpm ready ## Scope -- Keep the public API small: the Vite plugin, TypeScript config, and client directives. -- Prefer static routes and explicit TSX files. -- Document the decision before adding generated routes, content collections, rendering modes, or new client directives. -- Preserve the reference app as a simple content-site example. +- Keep the public API small: the Vite plugin, the type exports, the TypeScript config, and the `client:*` directives. +- Pages are static MDX files rendered through app layouts. Document the decision before adding rendering modes, generated routes, or content collections. +- UI components live in `packages/ui` and ship through the shadcn registry, not npm. +- The docs app in `apps/docs` is the reference app; keep it a simple content site. diff --git a/apps/docs/components.json b/apps/docs/components.json index fc691b6..d9441da 100644 --- a/apps/docs/components.json +++ b/apps/docs/components.json @@ -5,7 +5,7 @@ "tsx": true, "tailwind": { "config": "", - "css": "src/styles/globals.css", + "css": "src/index.css", "baseColor": "neutral", "cssVariables": true, "prefix": "" diff --git a/apps/docs/package.json b/apps/docs/package.json index f6f429f..316a3ac 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -10,29 +10,30 @@ }, "dependencies": { "@base-ui/react": "catalog:", - "@fontsource-variable/geist": "^5.2.9", + "@fontsource-variable/geist": "catalog:", "@fulldotdev/sitex": "workspace:*", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "lucide-react": "^1.16.0", - "react": "^19.1.1", - "react-dom": "^19.1.1", - "@shikijs/core": "^4.1.0", - "@shikijs/engine-javascript": "^4.1.0", - "@shikijs/langs": "^4.1.0", - "@shikijs/themes": "^4.1.0", - "tailwind-merge": "^3.6.0", - "tw-animate-css": "^1.4.0" + "@shikijs/core": "catalog:", + "@shikijs/engine-javascript": "catalog:", + "@shikijs/langs": "catalog:", + "@shikijs/themes": "catalog:", + "class-variance-authority": "catalog:", + "clsx": "catalog:", + "lucide-react": "catalog:", + "react": "catalog:", + "react-dom": "catalog:", + "schema-dts": "catalog:", + "tailwind-merge": "catalog:", + "tw-animate-css": "catalog:" }, "devDependencies": { - "@tailwindcss/vite": "^4.1.13", - "@types/react": "^19.1.12", - "@types/react-dom": "^19.1.9", - "@vitejs/plugin-react": "^5.0.2", - "shadcn": "^4.8.3", - "tailwindcss": "^4.1.13", - "typescript": "^5.9.2", - "vite": "^8.0.16", - "vite-plus": "^0.1.24" + "@tailwindcss/vite": "catalog:", + "@types/react": "catalog:", + "@types/react-dom": "catalog:", + "@vitejs/plugin-react": "catalog:", + "shadcn": "catalog:", + "tailwindcss": "catalog:", + "typescript": "catalog:", + "vite": "catalog:", + "vite-plus": "catalog:" } } diff --git a/apps/docs/public/favicon.svg b/apps/docs/public/favicon.svg deleted file mode 100644 index 2cd3557..0000000 --- a/apps/docs/public/favicon.svg +++ /dev/null @@ -1,7 +0,0 @@ -<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"> - <rect width="32" height="32" rx="7" fill="#ff5e00" /> - <path - d="M6.8 6.6 16 12.7l9.2-6.1-6.1 9.4 6.1 9.4-9.2-6.1-9.2 6.1 6.1-9.4z" - fill="#ffffff" - /> -</svg> diff --git a/apps/docs/src/assets/.gitkeep b/apps/docs/src/assets/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/apps/docs/src/assets/.gitkeep @@ -0,0 +1 @@ + diff --git a/apps/docs/src/components/blocks/sidebar-1.tsx b/apps/docs/src/components/blocks/sidebar-1.tsx index ee2d5c3..8c519ba 100644 --- a/apps/docs/src/components/blocks/sidebar-1.tsx +++ b/apps/docs/src/components/blocks/sidebar-1.tsx @@ -2,25 +2,10 @@ import { type ReactNode } from "react" -import { ChevronDownIcon, ExternalLinkIcon } from "lucide-react" +import { ExternalLinkIcon } from "lucide-react" import { ThemeProvider } from "@/components/theme-provider" -import { - Breadcrumb, - BreadcrumbEllipsis, - BreadcrumbItem, - BreadcrumbLink, - BreadcrumbList, - BreadcrumbPage, - BreadcrumbSeparator, -} from "@/components/ui/breadcrumb" import { Button } from "@/components/ui/button" -import { - DropdownMenu, - DropdownMenuContent, - DropdownMenuGroup, - DropdownMenuTrigger, -} from "@/components/ui/dropdown-menu" import { Header, HeaderContainer, HeaderGroup } from "@/components/ui/header" import { Logo, LogoImage, LogoText } from "@/components/ui/logo" import { Separator } from "@/components/ui/separator" @@ -43,29 +28,25 @@ import { cn } from "@/lib/utils" type NavigationGroup = { label: string href?: string - links?: { + links?: readonly { label: string href: string }[] } +type SidebarSection = { + label: string + href: string + navigation: readonly NavigationGroup[] +} + type Sidebar1Props = { className?: string logo: { label: string href: string } - breadcrumb: { - items: { - label: string - href: string - }[] - menu: { - label: string - href: string - }[] - } - navigation: NavigationGroup[] + sections: readonly SidebarSection[] githubRepo: string path: string children?: ReactNode @@ -77,24 +58,36 @@ function normalizePath(path: string) { return path.replace(/\/$/, "") } -function toDomId(value: string) { - return value.replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "") +function findActiveSection( + sections: readonly SidebarSection[], + currentPath: string +) { + const matches = sections.filter((section) => { + const sectionPath = normalizePath(section.href) + + return ( + currentPath === sectionPath || currentPath.startsWith(`${sectionPath}/`) + ) + }) + + return matches.sort((a, b) => b.href.length - a.href.length)[0] ?? sections[0] } function Sidebar1({ className, logo, - breadcrumb, - navigation, + sections, githubRepo, path, children, }: Sidebar1Props) { const currentPath = normalizePath(path) + const activeSection = findActiveSection(sections, currentPath) + const navigation = activeSection?.navigation ?? [] return ( <ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme"> - <SidebarProvider> + <SidebarProvider className="font-sans antialiased"> <Sidebar className={cn(className)} collapsible="offcanvas" @@ -111,6 +104,24 @@ function Sidebar1({ <LogoText className="truncate">Sitex</LogoText> </Logo> </SidebarMenuButton> + {sections.length > 1 ? ( + <div className="flex gap-1 rounded-lg bg-sidebar-accent/50 p-1"> + {sections.map((section) => ( + <a + key={section.href} + href={section.href} + className={cn( + "flex-1 rounded-md px-2 py-1 text-center text-xs font-medium transition-colors", + section === activeSection + ? "bg-background text-foreground shadow-sm" + : "text-muted-foreground hover:text-foreground" + )} + > + {section.label} + </a> + ))} + </div> + ) : null} </SidebarHeader> <SidebarContent className="mask-[linear-gradient(to_bottom,transparent,black_1rem,black_calc(100%-1rem),transparent)] [-webkit-mask-image:linear-gradient(to_bottom,transparent,black_1rem,black_calc(100%-1rem),transparent)] group-data-[collapsible=icon]:mask-none group-data-[collapsible=icon]:[-webkit-mask-image:none]"> {navigation.map((group) => ( @@ -141,14 +152,6 @@ function Sidebar1({ orientation="vertical" className="my-auto mr-2 h-4" /> - <Breadcrumb className="hidden md:block"> - <BreadcrumbList> - <BreadcrumbTrail - breadcrumb={breadcrumb} - currentPath={currentPath} - /> - </BreadcrumbList> - </Breadcrumb> </HeaderGroup> <HeaderGroup className="gap-1"> <Button @@ -185,132 +188,4 @@ function getNavigationItems(group: NavigationGroup) { return [] } -function BreadcrumbTrail({ - breadcrumb, - currentPath, -}: { - breadcrumb: Sidebar1Props["breadcrumb"] - currentPath: string -}) { - return ( - <> - {breadcrumb.items.map((item, index) => { - const isLast = index === breadcrumb.items.length - 1 - const isHome = item.href === "/" - const hasMenu = - !isHome && - breadcrumb.menu.some( - (menuItem) => - normalizePath(menuItem.href) === normalizePath(item.href) - ) - - return ( - <BreadcrumbFragment - currentPath={currentPath} - hasMenu={hasMenu} - isLast={isLast} - item={item} - key={`${item.href}-${index}`} - menu={breadcrumb.menu} - showSeparator={index > 0} - /> - ) - })} - {breadcrumb.menu.length > 0 && currentPath === "/" ? ( - <> - <BreadcrumbSeparator>/</BreadcrumbSeparator> - <BreadcrumbItem> - <DropdownMenu> - <DropdownMenuTrigger - aria-label="Open page menu" - className="inline-flex items-center leading-none transition-colors hover:text-foreground" - id="breadcrumb-page-menu" - > - <BreadcrumbEllipsis /> - </DropdownMenuTrigger> - <DropdownMenuContent align="start"> - <DropdownMenuGroup> - {breadcrumb.menu.map((item) => ( - <a - className="flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none hover:bg-accent hover:text-accent-foreground" - href={item.href} - key={item.href} - > - {item.label} - </a> - ))} - </DropdownMenuGroup> - </DropdownMenuContent> - </DropdownMenu> - </BreadcrumbItem> - </> - ) : null} - </> - ) -} - -function BreadcrumbFragment({ - currentPath, - hasMenu, - isLast, - item, - menu, - showSeparator, -}: { - currentPath: string - hasMenu: boolean - isLast: boolean - item: { label: string; href: string } - menu: { label: string; href: string }[] - showSeparator: boolean -}) { - return ( - <> - {showSeparator ? <BreadcrumbSeparator>/</BreadcrumbSeparator> : null} - <BreadcrumbItem> - {hasMenu ? ( - <DropdownMenu> - <DropdownMenuTrigger - aria-current={isLast ? "page" : undefined} - aria-label={`${item.label} pages`} - className={cn( - "inline-flex items-center gap-1 transition-colors hover:text-foreground", - isLast && "text-foreground" - )} - id={`breadcrumb-menu-${toDomId(item.href)}`} - > - {item.label} - <ChevronDownIcon className="size-3.5 opacity-70" aria-hidden /> - </DropdownMenuTrigger> - <DropdownMenuContent align="start"> - <DropdownMenuGroup> - {menu.map((menuItem) => ( - <a - aria-current={ - normalizePath(menuItem.href) === currentPath - ? "page" - : undefined - } - className="flex items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none select-none hover:bg-accent hover:text-accent-foreground aria-[current=page]:text-foreground" - href={menuItem.href} - key={menuItem.href} - > - {menuItem.label} - </a> - ))} - </DropdownMenuGroup> - </DropdownMenuContent> - </DropdownMenu> - ) : isLast ? ( - <BreadcrumbPage>{item.label}</BreadcrumbPage> - ) : ( - <BreadcrumbLink render={<a href={item.href} />}> - {item.label} - </BreadcrumbLink> - )} - </BreadcrumbItem> - </> - ) -} - export { Sidebar1, type Sidebar1Props } diff --git a/apps/docs/src/components/layouts/base.tsx b/apps/docs/src/components/layouts/base.tsx deleted file mode 100644 index a562296..0000000 --- a/apps/docs/src/components/layouts/base.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import type { ReactNode } from "react" - -import { Sidebar1 } from "@/components/blocks/sidebar-1" -import "@/styles/globals.css" - -export type BaseProps = { - title: string - description: string - path?: string - children?: ReactNode -} - -export type NavigationLink = { - href: string - label: string -} - -const docsNavigationGroups = [ - { - label: "Start", - links: [ - { href: "/docs", label: "Introduction" }, - { href: "/docs/installation", label: "Installation" }, - { href: "/docs/folder-structure", label: "Folder structure" }, - ], - }, - { - label: "Pages", - links: [ - { href: "/docs/page-routing", label: "Routing" }, - { href: "/docs/tsx-pages", label: "TSX pages" }, - { href: "/docs/mdx", label: "MDX pages" }, - { href: "/docs/rendering-and-assets", label: "Page rendering" }, - { href: "/docs/content", label: "Pages API" }, - ], - }, - { - label: "Components", - links: [ - { href: "/docs/island-rendering", label: "Component rendering" }, - { href: "/docs/mdx-components", label: "MDX components" }, - ], - }, -] - -export const docsNavigation: NavigationLink[] = docsNavigationGroups.flatMap( - (group) => group.links -) - -const site = { - name: "Sitex Docs", - logo: { - label: "Sitex", - href: "/", - }, - header: { - githubRepo: "fulldotdev/sitex", - navigation: docsNavigation, - }, - sidebar: { - navigation: docsNavigationGroups, - }, -} - -function normalizePath(path: string) { - if (path === "/") return path - - return path.replace(/\/$/, "") -} - -function getPageBreadcrumbItems(path: string) { - const currentPath = normalizePath(path) - - if (currentPath === "/") { - return [{ label: "Home", href: "/" }] - } - - const currentPage = docsNavigation.find( - (item) => normalizePath(item.href) === currentPath - ) - - return [{ label: "Home", href: "/" }, ...(currentPage ? [currentPage] : [])] -} - -export default function Base({ - title, - description, - path = "/", - children, -}: BaseProps) { - const breadcrumbItems = getPageBreadcrumbItems(path) - - return ( - <html - className="dark overscroll-none scroll-smooth bg-background text-foreground has-data-[variant=inset]:bg-sidebar" - data-slot="layout" - lang="en" - > - <head> - <meta charSet="utf-8" /> - <meta name="viewport" content="width=device-width, initial-scale=1" /> - <link rel="icon" type="image/svg+xml" href="/favicon.svg" /> - <title>{title} - - - - - - - - -
- {children} -
-
- - - ) -} diff --git a/apps/docs/src/components/layouts/doc.tsx b/apps/docs/src/components/layouts/doc.tsx deleted file mode 100644 index a9e3fb2..0000000 --- a/apps/docs/src/components/layouts/doc.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import type { ComponentProps } from "react" - -import { Doc1 } from "@/components/blocks/doc-1" -import Base, { docsNavigation } from "@/components/layouts/base" - -type Props = ComponentProps & { - doc: Omit< - ComponentProps, - "children" | "previousPage" | "nextPage" - > -} - -function normalizePath(path: string) { - if (path === "/") return path - - return path.replace(/\/$/, "") -} - -export default function Doc({ doc, path = "/", children, ...props }: Props) { - const currentPath = normalizePath(path) - const currentIndex = docsNavigation.findIndex( - (item) => normalizePath(item.href) === currentPath - ) - const previousPage = - currentIndex > 0 ? docsNavigation[currentIndex - 1] : undefined - const nextPage = - currentIndex >= 0 ? docsNavigation[currentIndex + 1] : undefined - - return ( - - - {children} - - - ) -} diff --git a/apps/docs/src/components/layouts/home.tsx b/apps/docs/src/components/layouts/home.tsx deleted file mode 100644 index a1fcd38..0000000 --- a/apps/docs/src/components/layouts/home.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import type { ComponentProps } from "react" - -import { Features1 } from "@/components/blocks/features-1" -import { Hero1 } from "@/components/blocks/hero-1" -import Base from "@/components/layouts/base" - -type Props = ComponentProps & { - hero: ComponentProps - features: ComponentProps -} - -export default function Home({ hero, features, ...props }: Props) { - return ( - - - - - ) -} diff --git a/apps/docs/src/components/ui/breadcrumb.tsx b/apps/docs/src/components/ui/breadcrumb.tsx deleted file mode 100644 index 4ef7e26..0000000 --- a/apps/docs/src/components/ui/breadcrumb.tsx +++ /dev/null @@ -1,122 +0,0 @@ -import * as React from "react" -import { mergeProps } from "@base-ui/react/merge-props" -import { useRender } from "@base-ui/react/use-render" - -import { cn } from "@/lib/utils" -import { ChevronRightIcon, MoreHorizontalIcon } from "lucide-react" - -function Breadcrumb({ className, ...props }: React.ComponentProps<"nav">) { - return ( -