+ BTST AI Chat Demo +
++ Available routes in this demo +
++ {group.heading} +
+-
+ {group.routes.map(({ label, path, description }) => (
+
-
+
+ ++{label}++ {description} ++
+ {path} ++ +
+ ))}
+
+ Available routes in this demo +
++ {group.heading} +
+
+ {path}
+
+
+ + Available routes in this demo +
++ {group.heading} +
+
+ {path}
+
+
+ + Available routes in this demo +
++ {group.heading} +
+
+ {path}
+
+
+ + Available routes in this demo +
++ {group.heading} +
+
+ {path}
+
+
+ + Available routes in this demo +
++ {group.heading} +
+
+ {path}
+
+
+ + Available routes in this demo +
++ {group.heading} +
+
+ {path}
+
+
+
{path}
diff --git a/demos/blog/app/page.tsx b/demos/blog/app/page.tsx
index 4c8c867e..1ce16b65 100644
--- a/demos/blog/app/page.tsx
+++ b/demos/blog/app/page.tsx
@@ -1,95 +1,64 @@
import Link from "next/link";
+import { getOrCreateQueryClient } from "@/lib/query-client";
+import { getStackClient } from "@/lib/stack-client";
+import { generateSchema } from "@btst/stack/plugins/route-docs/client";
+import { myStack } from "@/lib/stack";
-type RouteItem = {
- label: string;
- path: string;
- description: string;
-};
+type RouteItem = { label: string; path: string };
+type RouteGroup = { heading: string; routes: RouteItem[] };
-type RouteGroup = {
- heading: string;
- routes: RouteItem[];
-};
+const SITE_BASE_PATH = "/pages";
-const groups: RouteGroup[] = [
- {
- heading: "Blog",
- routes: [
- {
- label: "Blog",
- path: "/pages/blog",
- description: "All published posts",
- },
- {
- label: "Drafts",
- path: "/pages/blog/drafts",
- description: "Unpublished draft posts",
- },
- {
- label: "New Post",
- path: "/pages/blog/new",
- description: "Create a new blog post",
- },
- ],
- },
- {
- heading: "Posts (seeded)",
- routes: [
- {
- label: "Getting Started with BTST Blog",
- path: "/pages/blog/getting-started",
- description: "An introduction to the BTST blog plugin",
- },
- {
- label: "Building Full-Stack Apps with Plugins",
- path: "/pages/blog/full-stack-plugins",
- description: "Plugin-first approach to full-stack development",
- },
- {
- label: "SEO and Meta Tags in BTST",
- path: "/pages/blog/seo-and-meta-tags",
- description: "Auto-generated Open Graph and Twitter card meta tags",
- },
- ],
- },
- {
- heading: "Edit Posts (seeded)",
- routes: [
- {
- label: "Edit: Getting Started with BTST Blog",
- path: "/pages/blog/getting-started/edit",
- description: "Edit the getting started post",
- },
- {
- label: "Edit: Building Full-Stack Apps with Plugins",
- path: "/pages/blog/full-stack-plugins/edit",
- description: "Edit the full-stack plugins post",
- },
- {
- label: "Edit: SEO and Meta Tags in BTST",
- path: "/pages/blog/seo-and-meta-tags/edit",
- description: "Edit the SEO post",
- },
- ],
- },
- {
- heading: "Docs",
- routes: [
- {
- label: "Route Docs",
- path: "/pages/route-docs",
- description: "All client routes in this demo",
- },
- {
- label: "API Reference",
- path: "/api/data/reference",
- description: "OpenAPI reference for the backend",
- },
- ],
- },
-];
+function routeKeyToLabel(key: string): string {
+ return key
+ .replace(/([A-Z])/g, " $1")
+ .replace(/^./, (s) => s.toUpperCase())
+ .trim();
+}
+
+export default async function Home() {
+ const queryClient = getOrCreateQueryClient();
+ getStackClient(queryClient);
+ const schema = await generateSchema();
+
+ const blogPlugin = schema.plugins.find((p) => p.key === "blog");
+ const staticBlogRoutes: RouteItem[] =
+ blogPlugin?.routes
+ .filter((r) => r.pathParams.length === 0)
+ .map((r) => ({
+ label: routeKeyToLabel(r.key),
+ path: `${SITE_BASE_PATH}${r.path}`,
+ })) ?? [];
+
+ const { items: posts } = await myStack.api.blog.getAllPosts({
+ published: true,
+ });
+
+ const groups: RouteGroup[] = [
+ { heading: "Blog", routes: staticBlogRoutes },
+ {
+ heading: "Posts (seeded)",
+ routes: posts.map((p) => ({
+ label: p.title,
+ path: `${SITE_BASE_PATH}/blog/${p.slug}`,
+ })),
+ },
+ {
+ heading: "Edit Posts (seeded)",
+ routes: posts.map((p) => ({
+ label: p.title,
+ path: `${SITE_BASE_PATH}/blog/${p.slug}/edit`,
+ })),
+ },
+ {
+ heading: "Docs",
+ routes: [
+ { label: "Route Docs", path: `${SITE_BASE_PATH}/route-docs` },
+ { label: "API Reference", path: "/api/data/reference" },
+ ],
+ },
+ ].filter((g) => g.routes.length > 0);
-export default function Home() {
return (
{path}
diff --git a/demos/cms/app/page.tsx b/demos/cms/app/page.tsx
index dbd3fcd0..bab7b0f0 100644
--- a/demos/cms/app/page.tsx
+++ b/demos/cms/app/page.tsx
@@ -1,75 +1,70 @@
import Link from "next/link";
+import { getOrCreateQueryClient } from "@/lib/query-client";
+import { getStackClient } from "@/lib/stack-client";
+import { generateSchema } from "@btst/stack/plugins/route-docs/client";
+import { myStack } from "@/lib/stack";
-type RouteItem = {
- label: string;
- path: string;
- description: string;
-};
+type RouteItem = { label: string; path: string };
+type RouteGroup = { heading: string; routes: RouteItem[] };
-type RouteGroup = {
- heading: string;
- routes: RouteItem[];
-};
+const SITE_BASE_PATH = "/pages";
-const groups: RouteGroup[] = [
- {
- heading: "Articles (public)",
- routes: [
- {
- label: "Articles",
- path: "/pages/articles",
- description: "Public-facing article listing",
- },
- {
- label: "Welcome to BTST CMS",
- path: "/pages/articles/welcome-to-btst-cms",
- description: "Introduction to managing structured content",
- },
- {
- label: "Getting Started with Content Types",
- path: "/pages/articles/getting-started-with-content-types",
- description: "How to define and manage content types",
- },
- ],
- },
- {
- heading: "CMS (admin)",
- routes: [
- {
- label: "CMS Dashboard",
- path: "/pages/cms",
- description: "Manage content types and entries",
- },
- {
- label: "Article List",
- path: "/pages/cms/article",
- description: "All article content items",
- },
- {
- label: "New Article",
- path: "/pages/cms/article/new",
- description: "Create a new article",
- },
- ],
- },
- {
- heading: "Docs",
- routes: [
- {
- label: "Route Docs",
- path: "/pages/route-docs",
- description: "All client routes in this demo",
- },
- {
- label: "API Reference",
- path: "/api/data/reference",
- description: "OpenAPI reference for the backend",
- },
- ],
- },
-];
+function routeKeyToLabel(key: string): string {
+ return key
+ .replace(/([A-Z])/g, " $1")
+ .replace(/^./, (s) => s.toUpperCase())
+ .trim();
+}
+
+export default async function Home() {
+ const queryClient = getOrCreateQueryClient();
+ getStackClient(queryClient);
+ const schema = await generateSchema();
+
+ const cmsPlugin = schema.plugins.find((p) => p.key === "cms");
+ const staticCmsRoutes: RouteItem[] =
+ cmsPlugin?.routes
+ .filter((r) => r.pathParams.length === 0)
+ .map((r) => ({
+ label: routeKeyToLabel(r.key),
+ path: `${SITE_BASE_PATH}${r.path}`,
+ })) ?? [];
+
+ const [contentTypes, { items: articles }] = await Promise.all([
+ myStack.api.cms.getAllContentTypes(),
+ myStack.api.cms.getAllContentItems("article"),
+ ]);
+
+ // Expand parameterized CMS admin routes (/cms/:typeSlug, /cms/:typeSlug/new) with real content type slugs
+ const cmsTypeRoutes: RouteItem[] = contentTypes.flatMap((t) => [
+ { label: t.name, path: `${SITE_BASE_PATH}/cms/${t.slug}` },
+ { label: `New ${t.name}`, path: `${SITE_BASE_PATH}/cms/${t.slug}/new` },
+ ]);
+
+ const groups: RouteGroup[] = [
+ {
+ heading: "Articles (public)",
+ routes: [
+ { label: "Articles", path: `${SITE_BASE_PATH}/articles` },
+ ...articles.map((item) => ({
+ label: (item.parsedData as { title?: string }).title ?? item.slug,
+ path: `${SITE_BASE_PATH}/articles/${item.slug}`,
+ })),
+ ],
+ },
+ {
+ heading: "CMS (admin)",
+ routes: [...staticCmsRoutes, ...cmsTypeRoutes],
+ },
+ {
+ heading: "Docs",
+ routes: [
+ { label: "Route Docs", path: `${SITE_BASE_PATH}/route-docs` },
+ { label: "API Reference", path: "/api/data/reference" },
+ ],
+ },
+ ].filter((g) => g.routes.length > 0);
-export default function Home() {
return (
{path}
diff --git a/demos/form-builder/app/page.tsx b/demos/form-builder/app/page.tsx
index fe255ee9..fac5c354 100644
--- a/demos/form-builder/app/page.tsx
+++ b/demos/form-builder/app/page.tsx
@@ -1,65 +1,55 @@
import Link from "next/link";
+import { getOrCreateQueryClient } from "@/lib/query-client";
+import { getStackClient } from "@/lib/stack-client";
+import { generateSchema } from "@btst/stack/plugins/route-docs/client";
+import { myStack } from "@/lib/stack";
-type RouteItem = {
- label: string;
- path: string;
- description: string;
-};
+type RouteItem = { label: string; path: string };
+type RouteGroup = { heading: string; routes: RouteItem[] };
-type RouteGroup = {
- heading: string;
- routes: RouteItem[];
-};
+const SITE_BASE_PATH = "/pages";
-const groups: RouteGroup[] = [
- {
- heading: "Forms (admin)",
- routes: [
- {
- label: "Forms",
- path: "/pages/forms",
- description: "Browse and manage all forms",
- },
- {
- label: "New Form",
- path: "/pages/forms/new",
- description: "Create a new form",
- },
- ],
- },
- {
- heading: "Public Forms (seeded)",
- routes: [
- {
- label: "Contact Us",
- path: "/submit/contact-us",
- description: "Public contact form submission page",
- },
- {
- label: "Feedback Form",
- path: "/submit/feedback",
- description: "Public product feedback form",
- },
- ],
- },
- {
- heading: "Docs",
- routes: [
- {
- label: "Route Docs",
- path: "/pages/route-docs",
- description: "All client routes in this demo",
- },
- {
- label: "API Reference",
- path: "/api/data/reference",
- description: "OpenAPI reference for the backend",
- },
- ],
- },
-];
+function routeKeyToLabel(key: string): string {
+ return key
+ .replace(/([A-Z])/g, " $1")
+ .replace(/^./, (s) => s.toUpperCase())
+ .trim();
+}
+
+export default async function Home() {
+ const queryClient = getOrCreateQueryClient();
+ getStackClient(queryClient);
+ const schema = await generateSchema();
+
+ const formBuilderPlugin = schema.plugins.find((p) => p.key === "formBuilder");
+ const staticFormRoutes: RouteItem[] =
+ formBuilderPlugin?.routes
+ .filter((r) => r.pathParams.length === 0)
+ .map((r) => ({
+ label: routeKeyToLabel(r.key),
+ path: `${SITE_BASE_PATH}${r.path}`,
+ })) ?? [];
+
+ const { items: forms } = await myStack.api.formBuilder.getAllForms();
+
+ const groups: RouteGroup[] = [
+ { heading: "Forms (admin)", routes: staticFormRoutes },
+ {
+ heading: "Public Forms (seeded)",
+ routes: forms.map((f) => ({
+ label: f.name,
+ path: `/submit/${f.slug}`,
+ })),
+ },
+ {
+ heading: "Docs",
+ routes: [
+ { label: "Route Docs", path: `${SITE_BASE_PATH}/route-docs` },
+ { label: "API Reference", path: "/api/data/reference" },
+ ],
+ },
+ ].filter((g) => g.routes.length > 0);
-export default function Home() {
return (
{path}
diff --git a/demos/kanban/app/page.tsx b/demos/kanban/app/page.tsx
index e598a650..aa9166a4 100644
--- a/demos/kanban/app/page.tsx
+++ b/demos/kanban/app/page.tsx
@@ -1,60 +1,55 @@
import Link from "next/link";
+import { getOrCreateQueryClient } from "@/lib/query-client";
+import { getStackClient } from "@/lib/stack-client";
+import { generateSchema } from "@btst/stack/plugins/route-docs/client";
+import { myStack } from "@/lib/stack";
-type RouteItem = {
- label: string;
- path: string;
- description: string;
-};
+type RouteItem = { label: string; path: string };
+type RouteGroup = { heading: string; routes: RouteItem[] };
-type RouteGroup = {
- heading: string;
- routes: RouteItem[];
-};
+const SITE_BASE_PATH = "/pages";
-const groups: RouteGroup[] = [
- {
- heading: "Kanban",
- routes: [
- {
- label: "Boards",
- path: "/pages/kanban",
- description: "All kanban boards",
- },
- {
- label: "New Board",
- path: "/pages/kanban/new",
- description: "Create a new board",
- },
- ],
- },
- {
- heading: "Boards (seeded)",
- routes: [
- {
- label: "BTST Demo Board",
- path: "/pages/kanban/demo-board",
- description: "Pre-seeded demo board with sample tasks",
- },
- ],
- },
- {
- heading: "Docs",
- routes: [
- {
- label: "Route Docs",
- path: "/pages/route-docs",
- description: "All client routes in this demo",
- },
- {
- label: "API Reference",
- path: "/api/data/reference",
- description: "OpenAPI reference for the backend",
- },
- ],
- },
-];
+function routeKeyToLabel(key: string): string {
+ return key
+ .replace(/([A-Z])/g, " $1")
+ .replace(/^./, (s) => s.toUpperCase())
+ .trim();
+}
+
+export default async function Home() {
+ const queryClient = getOrCreateQueryClient();
+ getStackClient(queryClient);
+ const schema = await generateSchema();
+
+ const kanbanPlugin = schema.plugins.find((p) => p.key === "kanban");
+ const staticKanbanRoutes: RouteItem[] =
+ kanbanPlugin?.routes
+ .filter((r) => r.pathParams.length === 0)
+ .map((r) => ({
+ label: routeKeyToLabel(r.key),
+ path: `${SITE_BASE_PATH}${r.path}`,
+ })) ?? [];
+
+ const { items: boards } = await myStack.api.kanban.getAllBoards();
+
+ const groups: RouteGroup[] = [
+ { heading: "Kanban", routes: staticKanbanRoutes },
+ {
+ heading: "Boards (seeded)",
+ routes: boards.map((b) => ({
+ label: b.name,
+ path: `${SITE_BASE_PATH}/kanban/${b.id}`,
+ })),
+ },
+ {
+ heading: "Docs",
+ routes: [
+ { label: "Route Docs", path: `${SITE_BASE_PATH}/route-docs` },
+ { label: "API Reference", path: "/api/data/reference" },
+ ],
+ },
+ ].filter((g) => g.routes.length > 0);
-export default function Home() {
return (
{path}
diff --git a/demos/ui-builder/app/page.tsx b/demos/ui-builder/app/page.tsx
index cf349fc7..17651d89 100644
--- a/demos/ui-builder/app/page.tsx
+++ b/demos/ui-builder/app/page.tsx
@@ -1,65 +1,66 @@
import Link from "next/link";
+import { getOrCreateQueryClient } from "@/lib/query-client";
+import { getStackClient } from "@/lib/stack-client";
+import { generateSchema } from "@btst/stack/plugins/route-docs/client";
+import { myStack } from "@/lib/stack";
-type RouteItem = {
- label: string;
- path: string;
- description: string;
-};
+type RouteItem = { label: string; path: string };
+type RouteGroup = { heading: string; routes: RouteItem[] };
-type RouteGroup = {
- heading: string;
- routes: RouteItem[];
-};
+const SITE_BASE_PATH = "/pages";
-const groups: RouteGroup[] = [
- {
- heading: "UI Builder",
- routes: [
- {
- label: "Pages",
- path: "/pages/ui-builder",
- description: "All UI builder pages",
- },
- {
- label: "New Page",
- path: "/pages/ui-builder/new",
- description: "Create a new visual page",
- },
- ],
- },
- {
- heading: "CMS (admin)",
- routes: [
- {
- label: "CMS Dashboard",
- path: "/pages/cms",
- description: "Manage CMS content types and entries",
- },
- {
- label: "UI Builder Content List",
- path: "/pages/cms/ui-builder-page",
- description: "CMS list of all UI builder pages",
- },
- ],
- },
- {
- heading: "Docs",
- routes: [
- {
- label: "Route Docs",
- path: "/pages/route-docs",
- description: "All client routes in this demo",
- },
- {
- label: "API Reference",
- path: "/api/data/reference",
- description: "OpenAPI reference for the backend",
- },
- ],
- },
-];
+function routeKeyToLabel(key: string): string {
+ return key
+ .replace(/([A-Z])/g, " $1")
+ .replace(/^./, (s) => s.toUpperCase())
+ .trim();
+}
+
+export default async function Home() {
+ const queryClient = getOrCreateQueryClient();
+ getStackClient(queryClient);
+ const schema = await generateSchema();
+
+ const uiBuilderPlugin = schema.plugins.find((p) => p.key === "uiBuilder");
+ const staticUiBuilderRoutes: RouteItem[] =
+ uiBuilderPlugin?.routes
+ .filter((r) => r.pathParams.length === 0)
+ .map((r) => ({
+ label: routeKeyToLabel(r.key),
+ path: `${SITE_BASE_PATH}${r.path}`,
+ })) ?? [];
+
+ const cmsPlugin = schema.plugins.find((p) => p.key === "cms");
+ const staticCmsRoutes: RouteItem[] =
+ cmsPlugin?.routes
+ .filter((r) => r.pathParams.length === 0)
+ .map((r) => ({
+ label: routeKeyToLabel(r.key),
+ path: `${SITE_BASE_PATH}${r.path}`,
+ })) ?? [];
+
+ // Expand parameterized CMS admin routes with real content type slugs
+ const contentTypes = await myStack.api.cms.getAllContentTypes();
+ const cmsTypeRoutes: RouteItem[] = contentTypes.flatMap((t) => [
+ { label: t.name, path: `${SITE_BASE_PATH}/cms/${t.slug}` },
+ { label: `New ${t.name}`, path: `${SITE_BASE_PATH}/cms/${t.slug}/new` },
+ ]);
+
+ const groups: RouteGroup[] = [
+ { heading: "UI Builder", routes: staticUiBuilderRoutes },
+ {
+ heading: "CMS (admin)",
+ routes: [...staticCmsRoutes, ...cmsTypeRoutes],
+ },
+ {
+ heading: "Docs",
+ routes: [
+ { label: "Route Docs", path: `${SITE_BASE_PATH}/route-docs` },
+ { label: "API Reference", path: "/api/data/reference" },
+ ],
+ },
+ ].filter((g) => g.routes.length > 0);
-export default function Home() {
return (
{path}
From b22f97200d268631f87f5048e01c21d79c10b6fc Mon Sep 17 00:00:00 2001
From: olliethedev <3martynov@gmail.com>
Date: Sat, 7 Mar 2026 12:12:05 -0500
Subject: [PATCH 7/7] chore: update @btst/stack dependency to version 2.5.3
across all demo projects
---
demos/ai-chat/package.json | 2 +-
demos/blog/package.json | 2 +-
demos/cms/package.json | 2 +-
demos/form-builder/package.json | 2 +-
demos/kanban/package.json | 2 +-
demos/ui-builder/package.json | 2 +-
packages/stack/package.json | 2 +-
packages/stack/src/plugins/route-docs/generator.ts | 4 +++-
8 files changed, 10 insertions(+), 8 deletions(-)
diff --git a/demos/ai-chat/package.json b/demos/ai-chat/package.json
index 3386dc5d..8f92babc 100644
--- a/demos/ai-chat/package.json
+++ b/demos/ai-chat/package.json
@@ -11,7 +11,7 @@
"@ai-sdk/openai": "^3.0.41",
"@ai-sdk/react": "^3.0.118",
"@btst/adapter-memory": "^2.0.3",
- "@btst/stack": "^2.5.2",
+ "@btst/stack": "^2.5.3",
"@btst/yar": "^1.2.0",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-dialog": "^1.1.15",
diff --git a/demos/blog/package.json b/demos/blog/package.json
index 3ed8d559..724821ac 100644
--- a/demos/blog/package.json
+++ b/demos/blog/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@ai-sdk/react": "^3.0.118",
"@btst/adapter-memory": "^2.0.3",
- "@btst/stack": "^2.5.2",
+ "@btst/stack": "^2.5.3",
"@btst/yar": "^1.2.0",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-dialog": "^1.1.15",
diff --git a/demos/cms/package.json b/demos/cms/package.json
index 0491f309..8b658223 100644
--- a/demos/cms/package.json
+++ b/demos/cms/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@ai-sdk/react": "^3.0.118",
"@btst/adapter-memory": "^2.0.3",
- "@btst/stack": "^2.5.2",
+ "@btst/stack": "^2.5.3",
"@btst/yar": "^1.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
diff --git a/demos/form-builder/package.json b/demos/form-builder/package.json
index dddcda1d..90b616a1 100644
--- a/demos/form-builder/package.json
+++ b/demos/form-builder/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@ai-sdk/react": "^3.0.118",
"@btst/adapter-memory": "^2.0.3",
- "@btst/stack": "^2.5.2",
+ "@btst/stack": "^2.5.3",
"@btst/yar": "^1.2.0",
"@hookform/resolvers": "^5.2.2",
"@radix-ui/react-checkbox": "^1.3.3",
diff --git a/demos/kanban/package.json b/demos/kanban/package.json
index b2cac79c..7fcb31f8 100644
--- a/demos/kanban/package.json
+++ b/demos/kanban/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@ai-sdk/react": "^3.0.118",
"@btst/adapter-memory": "^2.0.3",
- "@btst/stack": "^2.5.2",
+ "@btst/stack": "^2.5.3",
"@btst/yar": "^1.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
diff --git a/demos/ui-builder/package.json b/demos/ui-builder/package.json
index ca8d856b..a45e7ec8 100644
--- a/demos/ui-builder/package.json
+++ b/demos/ui-builder/package.json
@@ -10,7 +10,7 @@
"dependencies": {
"@ai-sdk/react": "^3.0.118",
"@btst/adapter-memory": "^2.0.3",
- "@btst/stack": "^2.5.2",
+ "@btst/stack": "^2.5.3",
"@btst/yar": "^1.2.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
diff --git a/packages/stack/package.json b/packages/stack/package.json
index 749c22ca..de6181b9 100644
--- a/packages/stack/package.json
+++ b/packages/stack/package.json
@@ -1,6 +1,6 @@
{
"name": "@btst/stack",
- "version": "2.5.2",
+ "version": "2.5.3",
"description": "A composable, plugin-based library for building full-stack applications.",
"repository": {
"type": "git",
diff --git a/packages/stack/src/plugins/route-docs/generator.ts b/packages/stack/src/plugins/route-docs/generator.ts
index 3e02f260..50ad9bfd 100644
--- a/packages/stack/src/plugins/route-docs/generator.ts
+++ b/packages/stack/src/plugins/route-docs/generator.ts
@@ -143,7 +143,9 @@ function processZodType(zodType: z.ZodType