diff --git a/apps/cli/package.json b/apps/cli/package.json
index e5d1a96..fe8832c 100644
--- a/apps/cli/package.json
+++ b/apps/cli/package.json
@@ -19,9 +19,6 @@
"url": "git+https://github.com/jakejarvis/stanza.git",
"directory": "apps/cli"
},
- "bin": {
- "stanza": "./dist/bin.mjs"
- },
"files": [
"dist"
],
@@ -32,6 +29,9 @@
".": "./src/index.ts"
},
"publishConfig": {
+ "bin": {
+ "stanza": "./dist/bin.mjs"
+ },
"main": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"exports": {
diff --git a/apps/web/src/components/builder/file-preview.tsx b/apps/web/src/components/builder/file-preview.tsx
index d7689e3..ff00f78 100644
--- a/apps/web/src/components/builder/file-preview.tsx
+++ b/apps/web/src/components/builder/file-preview.tsx
@@ -114,7 +114,7 @@ export function FilePreview({
diff --git a/apps/web/src/components/docs/docs-sidebar.tsx b/apps/web/src/components/docs/docs-sidebar.tsx
new file mode 100644
index 0000000..417c861
--- /dev/null
+++ b/apps/web/src/components/docs/docs-sidebar.tsx
@@ -0,0 +1,93 @@
+import { Link, useRouterState } from "@tanstack/react-router";
+import type { Node, Root } from "fumadocs-core/page-tree";
+import type { ReactNode } from "react";
+
+import { cn } from "@/lib/utils";
+
+function toSplat(url: string): string {
+ return url.replace(/^\/docs\/?/, "");
+}
+
+function normalize(path: string): string {
+ return path.length > 1 ? path.replace(/\/$/, "") : path;
+}
+
+function NavLink({ url, name, pathname }: { url: string; name: ReactNode; pathname: string }) {
+ const active = normalize(pathname) === normalize(url);
+ return (
+
+
+ {name}
+
+
+ );
+}
+
+function NavNode({ node, pathname }: { node: Node; pathname: string }) {
+ if (node.type === "separator") {
+ return (
+
+ {node.name}
+
+ );
+ }
+
+ if (node.type === "folder") {
+ return (
+
+
+ {node.name}
+
+
+ {node.index && (
+
+ )}
+ {node.children.map((child, i) => (
+
+ ))}
+
+
+ );
+ }
+
+ return ;
+}
+
+function NavList({ nodes, pathname }: { nodes: Node[]; pathname: string }) {
+ return (
+
+ {nodes.map((node, i) => (
+
+ ))}
+
+ );
+}
+
+export function DocsSidebar({ tree }: { tree: Root }) {
+ const pathname = useRouterState({ select: (s) => s.location.pathname });
+ const list = ;
+
+ return (
+ <>
+ {/* Mobile: a disclosure above the content so docs stay navigable < md. */}
+
+ Documentation
+ {list}
+
+
+ {/* Desktop: sticky left rail aligned to the page frame. */}
+
+ >
+ );
+}
diff --git a/apps/web/src/components/docs/docs-toc.tsx b/apps/web/src/components/docs/docs-toc.tsx
new file mode 100644
index 0000000..42f3785
--- /dev/null
+++ b/apps/web/src/components/docs/docs-toc.tsx
@@ -0,0 +1,48 @@
+import { AnchorProvider, useActiveAnchor } from "fumadocs-core/toc";
+import type { TOCItemType } from "fumadocs-core/toc";
+
+import { cn } from "@/lib/utils";
+
+function TocList({ toc }: { toc: TOCItemType[] }) {
+ const active = useActiveAnchor();
+
+ return (
+
+ {toc.map((item) => {
+ const id = item.url.split("#")[1];
+ const isActive = id === active;
+ return (
+ -
+
+ {item.title}
+
+
+ );
+ })}
+
+ );
+}
+
+export function DocsToc({ toc }: { toc: TOCItemType[] }) {
+ if (toc.length === 0) return null;
+
+ return (
+
+ );
+}
diff --git a/apps/web/src/lib/layout.shared.tsx b/apps/web/src/lib/layout.shared.tsx
deleted file mode 100644
index e3cc75c..0000000
--- a/apps/web/src/lib/layout.shared.tsx
+++ /dev/null
@@ -1,13 +0,0 @@
-import type { BaseLayoutProps } from "fumadocs-ui/layouts/shared";
-
-// The stanza site header/footer wrap docs from __root, so Fumadocs renders no
-// top nav of its own — only its sidebar + table of contents. Dark mode and
-// search are owned by the site shell (ThemeProvider + registry palette), so
-// Fumadocs' own theme switch and search trigger stay off.
-export function baseOptions(): BaseLayoutProps {
- return {
- nav: { enabled: false },
- themeSwitch: { enabled: false },
- searchToggle: { enabled: false },
- };
-}
diff --git a/apps/web/src/routes/docs.$.tsx b/apps/web/src/routes/docs.$.tsx
index e31e9c6..aff00bb 100644
--- a/apps/web/src/routes/docs.$.tsx
+++ b/apps/web/src/routes/docs.$.tsx
@@ -2,13 +2,13 @@ import { createFileRoute, notFound } from "@tanstack/react-router";
import { createServerFn } from "@tanstack/react-start";
import browserCollections from "collections/browser";
import { useFumadocsLoader } from "fumadocs-core/source/client";
-import { DocsLayout } from "fumadocs-ui/layouts/docs";
-import { DocsBody, DocsDescription, DocsPage, DocsTitle } from "fumadocs-ui/layouts/docs/page";
+import { DocsBody } from "fumadocs-ui/layouts/docs/page";
import { RootProvider } from "fumadocs-ui/provider/tanstack";
import { Suspense } from "react";
+import { DocsSidebar } from "@/components/docs/docs-sidebar";
+import { DocsToc } from "@/components/docs/docs-toc";
import { useMDXComponents } from "@/components/mdx";
-import { baseOptions } from "@/lib/layout.shared";
import { buildHead } from "@/lib/seo";
import { source } from "@/lib/source";
@@ -31,13 +31,18 @@ const clientLoader = browserCollections.docs.createClientLoader({
id: "docs",
component({ toc, frontmatter, default: MDX }) {
return (
-
- {frontmatter.title}
- {frontmatter.description}
-
-
-
-
+
+
+ {frontmatter.title}
+ {frontmatter.description && (
+ {frontmatter.description}
+ )}
+
+
+
+
+
+
);
},
});
@@ -65,9 +70,14 @@ function Page() {
return (
-
- {clientLoader.useContent(data.path)}
-
+
+
+
+
+ {clientLoader.useContent(data.path)}
+
+
+
);
}
diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css
index 5d893ce..d00f2a1 100644
--- a/apps/web/src/styles.css
+++ b/apps/web/src/styles.css
@@ -8,12 +8,10 @@
@source "../node_modules/fumadocs-ui/dist/**/*.js";
/* Remap Fumadocs' own design tokens onto the stanza (shadcn) palette so the
- * docs chrome inherits the site's exact colors. The `#nd-sidebar` selector is
- * included to override fumadocs' neutral.css sidebar tweaks at equal
- * specificity; values follow stanza's `:root`/`.dark` tokens automatically. */
+ * docs content (prose, code blocks, callouts) inherits the site's exact colors.
+ * Values follow stanza's `:root`/`.dark` tokens automatically. */
:root,
-.dark,
-.dark #nd-sidebar {
+.dark {
--color-fd-background: var(--background);
--color-fd-foreground: var(--foreground);
--color-fd-muted: var(--muted);
diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts
index 137b59d..a43d245 100644
--- a/apps/web/vite.config.ts
+++ b/apps/web/vite.config.ts
@@ -17,5 +17,11 @@ export default defineConfig({
],
resolve: {
tsconfigPaths: true,
+ alias: {
+ // tslib's CJS UMD sets __esModule: true without providing a default
+ // export, which breaks Vite 8 / Rolldown's consistent CJS interop.
+ // Alias to the native ESM build to avoid the interop entirely.
+ tslib: "tslib/tslib.es6.mjs",
+ },
},
});
diff --git a/packages/create-stanza/package.json b/packages/create-stanza/package.json
index bd59e8b..4286b98 100644
--- a/packages/create-stanza/package.json
+++ b/packages/create-stanza/package.json
@@ -18,14 +18,14 @@
"url": "git+https://github.com/jakejarvis/stanza.git",
"directory": "packages/create-stanza"
},
- "bin": {
- "create-stanza": "./dist/bin.mjs"
- },
"files": [
"dist"
],
"type": "module",
"publishConfig": {
+ "bin": {
+ "create-stanza": "./dist/bin.mjs"
+ },
"access": "public"
},
"scripts": {