diff --git a/demos/ai-chat/.gitignore b/demos/ai-chat/.gitignore index 7fc49260..abb3b440 100644 --- a/demos/ai-chat/.gitignore +++ b/demos/ai-chat/.gitignore @@ -12,3 +12,5 @@ # misc .DS_Store *.pem +.btst-stack-src/ +.btst-stack-ui/ diff --git a/demos/ai-chat/app/globals.css b/demos/ai-chat/app/globals.css index 23035e76..df29e2ce 100644 --- a/demos/ai-chat/app/globals.css +++ b/demos/ai-chat/app/globals.css @@ -2,6 +2,15 @@ @import "tw-animate-css"; @import "@btst/stack/plugins/ai-chat/css"; @import "@btst/stack/plugins/route-docs/css"; +/* WebContainers workaround: the WASM Tailwind scanner cannot traverse node_modules + * (https://github.com/tailwindlabs/tailwindcss/issues/18418). copy-stack-src.mjs + * (run via predev/prebuild) copies @btst/stack/src outside node_modules so Tailwind + * can scan it. Safe to remove once the upstream bug is fixed. */ +@source "./.btst-stack-src/**/*.{ts,tsx}"; +@source "./.btst-stack-ui/**/*.{ts,tsx}"; +/* Monorepo fallback: direct source path used in local development */ +@source "../../../packages/stack/src/**/*.{ts,tsx}"; +@source "../../../packages/ui/src/**/*.{ts,tsx}"; @custom-variant dark (&:is(.dark *)); diff --git a/demos/ai-chat/copy-stack-src.mjs b/demos/ai-chat/copy-stack-src.mjs new file mode 100644 index 00000000..dc457f07 --- /dev/null +++ b/demos/ai-chat/copy-stack-src.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * Copies @btst/stack/src outside of node_modules so Tailwind's WASM scanner + * (used in WebContainers/StackBlitz) can traverse it. + * + * The WASM oxide scanner cannot scan inside node_modules due to a bug: + * https://github.com/tailwindlabs/tailwindcss/issues/18418 + * + * Run automatically via the predev/prebuild npm lifecycle hooks. + */ +import { cp, mkdir, rm } from "fs/promises"; +import { existsSync } from "fs"; + +const src = "node_modules/@btst/stack/src"; +const dest = ".btst-stack-src"; + +const uiSrc = "node_modules/@btst/stack/dist/packages/ui"; +const uiDest = ".btst-stack-ui"; + +if (!existsSync(src)) { + // Likely running in the monorepo where the workspace symlink does not expose + // src/ — fall back gracefully; @source paths in globals.css cover this case. + console.log( + "[copy-stack-src] node_modules/@btst/stack/src not found, skipping", + ); + process.exit(0); +} + +await rm(dest, { recursive: true, force: true }); +await mkdir(dest, { recursive: true }); +await cp(src, dest, { recursive: true }); +console.log("[copy-stack-src] copied @btst/stack/src → .btst-stack-src"); + +if (existsSync(uiSrc)) { + await rm(uiDest, { recursive: true, force: true }); + await mkdir(uiDest, { recursive: true }); + await cp(uiSrc, uiDest, { recursive: true }); + console.log( + "[copy-stack-src] copied @btst/stack/dist/packages/ui → .btst-stack-ui", + ); +} else { + console.log( + "[copy-stack-src] node_modules/@btst/stack/dist/packages/ui not found, skipping", + ); +} diff --git a/demos/ai-chat/package.json b/demos/ai-chat/package.json index 116c12b9..3386dc5d 100644 --- a/demos/ai-chat/package.json +++ b/demos/ai-chat/package.json @@ -3,15 +3,15 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", - "build": "next build", + "dev": "node copy-stack-src.mjs && next dev", + "build": "node copy-stack-src.mjs && next build", "start": "next start" }, "dependencies": { "@ai-sdk/openai": "^3.0.41", "@ai-sdk/react": "^3.0.118", "@btst/adapter-memory": "^2.0.3", - "@btst/stack": "^2.5.1", + "@btst/stack": "^2.5.2", "@btst/yar": "^1.2.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-dialog": "^1.1.15", @@ -29,7 +29,7 @@ "highlight.js": "^11.11.1", "katex": "^0.16.35", "lucide-react": "^0.577.0", - "next": "16.1.6", + "next": "15.4.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.3", diff --git a/demos/blog/.gitignore b/demos/blog/.gitignore index 7fc49260..abb3b440 100644 --- a/demos/blog/.gitignore +++ b/demos/blog/.gitignore @@ -12,3 +12,5 @@ # misc .DS_Store *.pem +.btst-stack-src/ +.btst-stack-ui/ diff --git a/demos/blog/app/globals.css b/demos/blog/app/globals.css index 0640acda..2829890b 100644 --- a/demos/blog/app/globals.css +++ b/demos/blog/app/globals.css @@ -2,6 +2,15 @@ @import "tw-animate-css"; @import "@btst/stack/plugins/blog/css"; @import "@btst/stack/plugins/route-docs/css"; +/* WebContainers workaround: the WASM Tailwind scanner cannot traverse node_modules + * (https://github.com/tailwindlabs/tailwindcss/issues/18418). copy-stack-src.mjs + * (run via predev/prebuild) copies @btst/stack/src outside node_modules so Tailwind + * can scan it. Safe to remove once the upstream bug is fixed. */ +@source "./.btst-stack-src/**/*.{ts,tsx}"; +@source "./.btst-stack-ui/**/*.{ts,tsx}"; +/* Monorepo fallback: direct source path used in local development */ +@source "../../../packages/stack/src/**/*.{ts,tsx}"; +@source "../../../packages/ui/src/**/*.{ts,tsx}"; @custom-variant dark (&:is(.dark *)); diff --git a/demos/blog/copy-stack-src.mjs b/demos/blog/copy-stack-src.mjs new file mode 100644 index 00000000..dc457f07 --- /dev/null +++ b/demos/blog/copy-stack-src.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * Copies @btst/stack/src outside of node_modules so Tailwind's WASM scanner + * (used in WebContainers/StackBlitz) can traverse it. + * + * The WASM oxide scanner cannot scan inside node_modules due to a bug: + * https://github.com/tailwindlabs/tailwindcss/issues/18418 + * + * Run automatically via the predev/prebuild npm lifecycle hooks. + */ +import { cp, mkdir, rm } from "fs/promises"; +import { existsSync } from "fs"; + +const src = "node_modules/@btst/stack/src"; +const dest = ".btst-stack-src"; + +const uiSrc = "node_modules/@btst/stack/dist/packages/ui"; +const uiDest = ".btst-stack-ui"; + +if (!existsSync(src)) { + // Likely running in the monorepo where the workspace symlink does not expose + // src/ — fall back gracefully; @source paths in globals.css cover this case. + console.log( + "[copy-stack-src] node_modules/@btst/stack/src not found, skipping", + ); + process.exit(0); +} + +await rm(dest, { recursive: true, force: true }); +await mkdir(dest, { recursive: true }); +await cp(src, dest, { recursive: true }); +console.log("[copy-stack-src] copied @btst/stack/src → .btst-stack-src"); + +if (existsSync(uiSrc)) { + await rm(uiDest, { recursive: true, force: true }); + await mkdir(uiDest, { recursive: true }); + await cp(uiSrc, uiDest, { recursive: true }); + console.log( + "[copy-stack-src] copied @btst/stack/dist/packages/ui → .btst-stack-ui", + ); +} else { + console.log( + "[copy-stack-src] node_modules/@btst/stack/dist/packages/ui not found, skipping", + ); +} diff --git a/demos/blog/lib/seed.ts b/demos/blog/lib/seed.ts index 1ebde118..8b952314 100644 --- a/demos/blog/lib/seed.ts +++ b/demos/blog/lib/seed.ts @@ -1,11 +1,6 @@ import type { Adapter } from "@btst/stack/plugins/api"; -let seeded = false; - export async function seedBlogData(adapter: Adapter) { - if (seeded) return; - seeded = true; - try { // Check if posts already exist const existing = await adapter.findMany({ model: "post", limit: 1 }); diff --git a/demos/blog/lib/stack.ts b/demos/blog/lib/stack.ts index 934d6b4a..44286f5a 100644 --- a/demos/blog/lib/stack.ts +++ b/demos/blog/lib/stack.ts @@ -4,8 +4,11 @@ import { blogBackendPlugin } from "@btst/stack/plugins/blog/api"; import { openApiBackendPlugin } from "@btst/stack/plugins/open-api/api"; import { seedBlogData } from "./seed"; +// Persist stack and seed promise on `global` so Next.js module re-evaluations +// (HMR, per-request server components) don't create a second instance or re-run the seed. const globalForStack = global as typeof global & { __btst_stack__?: ReturnType; + __btst_seeded__?: Promise; }; function createStack() { @@ -25,7 +28,8 @@ function createStack() { export const myStack = (globalForStack.__btst_stack__ ??= createStack()); -// Seed demo data after singleton is created -seedBlogData(myStack.adapter).catch(console.error); +globalForStack.__btst_seeded__ ??= seedBlogData(myStack.adapter).catch( + console.error, +); export const { handler, dbSchema } = myStack; diff --git a/demos/blog/package.json b/demos/blog/package.json index 798f9f3b..3ed8d559 100644 --- a/demos/blog/package.json +++ b/demos/blog/package.json @@ -3,14 +3,14 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", - "build": "next build", + "dev": "node copy-stack-src.mjs && next dev", + "build": "node copy-stack-src.mjs && next build", "start": "next start" }, "dependencies": { "@ai-sdk/react": "^3.0.118", "@btst/adapter-memory": "^2.0.3", - "@btst/stack": "^2.5.1", + "@btst/stack": "^2.5.2", "@btst/yar": "^1.2.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-dialog": "^1.1.15", @@ -28,7 +28,7 @@ "highlight.js": "^11.11.1", "katex": "^0.16.35", "lucide-react": "^0.577.0", - "next": "16.1.6", + "next": "15.4.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.3", diff --git a/demos/cms/.gitignore b/demos/cms/.gitignore index 7fc49260..abb3b440 100644 --- a/demos/cms/.gitignore +++ b/demos/cms/.gitignore @@ -12,3 +12,5 @@ # misc .DS_Store *.pem +.btst-stack-src/ +.btst-stack-ui/ diff --git a/demos/cms/app/globals.css b/demos/cms/app/globals.css index 62dce9a0..5b5e5234 100644 --- a/demos/cms/app/globals.css +++ b/demos/cms/app/globals.css @@ -2,6 +2,15 @@ @import "tw-animate-css"; @import "@btst/stack/plugins/cms/css"; @import "@btst/stack/plugins/route-docs/css"; +/* WebContainers workaround: the WASM Tailwind scanner cannot traverse node_modules + * (https://github.com/tailwindlabs/tailwindcss/issues/18418). copy-stack-src.mjs + * (run via predev/prebuild) copies @btst/stack/src outside node_modules so Tailwind + * can scan it. Safe to remove once the upstream bug is fixed. */ +@source "./.btst-stack-src/**/*.{ts,tsx}"; +@source "./.btst-stack-ui/**/*.{ts,tsx}"; +/* Monorepo fallback: direct source path used in local development */ +@source "../../../packages/stack/src/**/*.{ts,tsx}"; +@source "../../../packages/ui/src/**/*.{ts,tsx}"; @custom-variant dark (&:is(.dark *)); diff --git a/demos/cms/copy-stack-src.mjs b/demos/cms/copy-stack-src.mjs new file mode 100644 index 00000000..dc457f07 --- /dev/null +++ b/demos/cms/copy-stack-src.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * Copies @btst/stack/src outside of node_modules so Tailwind's WASM scanner + * (used in WebContainers/StackBlitz) can traverse it. + * + * The WASM oxide scanner cannot scan inside node_modules due to a bug: + * https://github.com/tailwindlabs/tailwindcss/issues/18418 + * + * Run automatically via the predev/prebuild npm lifecycle hooks. + */ +import { cp, mkdir, rm } from "fs/promises"; +import { existsSync } from "fs"; + +const src = "node_modules/@btst/stack/src"; +const dest = ".btst-stack-src"; + +const uiSrc = "node_modules/@btst/stack/dist/packages/ui"; +const uiDest = ".btst-stack-ui"; + +if (!existsSync(src)) { + // Likely running in the monorepo where the workspace symlink does not expose + // src/ — fall back gracefully; @source paths in globals.css cover this case. + console.log( + "[copy-stack-src] node_modules/@btst/stack/src not found, skipping", + ); + process.exit(0); +} + +await rm(dest, { recursive: true, force: true }); +await mkdir(dest, { recursive: true }); +await cp(src, dest, { recursive: true }); +console.log("[copy-stack-src] copied @btst/stack/src → .btst-stack-src"); + +if (existsSync(uiSrc)) { + await rm(uiDest, { recursive: true, force: true }); + await mkdir(uiDest, { recursive: true }); + await cp(uiSrc, uiDest, { recursive: true }); + console.log( + "[copy-stack-src] copied @btst/stack/dist/packages/ui → .btst-stack-ui", + ); +} else { + console.log( + "[copy-stack-src] node_modules/@btst/stack/dist/packages/ui not found, skipping", + ); +} diff --git a/demos/cms/lib/seed.ts b/demos/cms/lib/seed.ts index 44788c88..727001a8 100644 --- a/demos/cms/lib/seed.ts +++ b/demos/cms/lib/seed.ts @@ -1,9 +1,4 @@ -let seeded = false; - export async function seedCmsData(api: any) { - if (seeded) return; - seeded = true; - try { // Check if articles already exist (getAllContentItems also calls ensureSynced) const existing = await api.cms.getAllContentItems("article", { limit: 1 }); diff --git a/demos/cms/lib/stack.ts b/demos/cms/lib/stack.ts index d311a77d..23dc8835 100644 --- a/demos/cms/lib/stack.ts +++ b/demos/cms/lib/stack.ts @@ -4,8 +4,11 @@ import { cmsBackendPlugin } from "@btst/stack/plugins/cms/api"; import { openApiBackendPlugin } from "@btst/stack/plugins/open-api/api"; import { z } from "zod"; import { seedCmsData } from "./seed"; +// Persist stack and seed promise on `global` so Next.js module re-evaluations +// (HMR, per-request server components) don't create a second instance or re-run the seed. const globalForStack = global as typeof global & { __btst_stack__?: ReturnType; + __btst_seeded__?: Promise; }; // Simple article content type @@ -45,7 +48,8 @@ function createStack() { export const myStack = (globalForStack.__btst_stack__ ??= createStack()); -// Seed demo data — uses api.cms.createContentItem which calls ensureSynced internally -seedCmsData(myStack.api).catch(console.error); +globalForStack.__btst_seeded__ ??= seedCmsData(myStack.api).catch( + console.error, +); export const { handler, dbSchema } = myStack; diff --git a/demos/cms/package.json b/demos/cms/package.json index e0da6aff..0491f309 100644 --- a/demos/cms/package.json +++ b/demos/cms/package.json @@ -3,14 +3,14 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", - "build": "next build", + "dev": "node copy-stack-src.mjs && next dev", + "build": "node copy-stack-src.mjs && next build", "start": "next start" }, "dependencies": { "@ai-sdk/react": "^3.0.118", "@btst/adapter-memory": "^2.0.3", - "@btst/stack": "^2.5.1", + "@btst/stack": "^2.5.2", "@btst/yar": "^1.2.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -45,7 +45,7 @@ "highlight.js": "^11.11.1", "katex": "^0.16.35", "lucide-react": "^0.577.0", - "next": "16.1.6", + "next": "15.4.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.3", diff --git a/demos/form-builder/.gitignore b/demos/form-builder/.gitignore index 7fc49260..abb3b440 100644 --- a/demos/form-builder/.gitignore +++ b/demos/form-builder/.gitignore @@ -12,3 +12,5 @@ # misc .DS_Store *.pem +.btst-stack-src/ +.btst-stack-ui/ diff --git a/demos/form-builder/app/globals.css b/demos/form-builder/app/globals.css index df1dbb9c..dc6bac34 100644 --- a/demos/form-builder/app/globals.css +++ b/demos/form-builder/app/globals.css @@ -2,6 +2,15 @@ @import "tw-animate-css"; @import "@btst/stack/plugins/form-builder/css"; @import "@btst/stack/plugins/route-docs/css"; +/* WebContainers workaround: the WASM Tailwind scanner cannot traverse node_modules + * (https://github.com/tailwindlabs/tailwindcss/issues/18418). copy-stack-src.mjs + * (run via predev/prebuild) copies @btst/stack/src outside node_modules so Tailwind + * can scan it. Safe to remove once the upstream bug is fixed. */ +@source "./.btst-stack-src/**/*.{ts,tsx}"; +@source "./.btst-stack-ui/**/*.{ts,tsx}"; +/* Monorepo fallback: direct source path used in local development */ +@source "../../../packages/stack/src/**/*.{ts,tsx}"; +@source "../../../packages/ui/src/**/*.{ts,tsx}"; @custom-variant dark (&:is(.dark *)); diff --git a/demos/form-builder/copy-stack-src.mjs b/demos/form-builder/copy-stack-src.mjs new file mode 100644 index 00000000..dc457f07 --- /dev/null +++ b/demos/form-builder/copy-stack-src.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * Copies @btst/stack/src outside of node_modules so Tailwind's WASM scanner + * (used in WebContainers/StackBlitz) can traverse it. + * + * The WASM oxide scanner cannot scan inside node_modules due to a bug: + * https://github.com/tailwindlabs/tailwindcss/issues/18418 + * + * Run automatically via the predev/prebuild npm lifecycle hooks. + */ +import { cp, mkdir, rm } from "fs/promises"; +import { existsSync } from "fs"; + +const src = "node_modules/@btst/stack/src"; +const dest = ".btst-stack-src"; + +const uiSrc = "node_modules/@btst/stack/dist/packages/ui"; +const uiDest = ".btst-stack-ui"; + +if (!existsSync(src)) { + // Likely running in the monorepo where the workspace symlink does not expose + // src/ — fall back gracefully; @source paths in globals.css cover this case. + console.log( + "[copy-stack-src] node_modules/@btst/stack/src not found, skipping", + ); + process.exit(0); +} + +await rm(dest, { recursive: true, force: true }); +await mkdir(dest, { recursive: true }); +await cp(src, dest, { recursive: true }); +console.log("[copy-stack-src] copied @btst/stack/src → .btst-stack-src"); + +if (existsSync(uiSrc)) { + await rm(uiDest, { recursive: true, force: true }); + await mkdir(uiDest, { recursive: true }); + await cp(uiSrc, uiDest, { recursive: true }); + console.log( + "[copy-stack-src] copied @btst/stack/dist/packages/ui → .btst-stack-ui", + ); +} else { + console.log( + "[copy-stack-src] node_modules/@btst/stack/dist/packages/ui not found, skipping", + ); +} diff --git a/demos/form-builder/lib/seed.ts b/demos/form-builder/lib/seed.ts index a8166a0f..21738fda 100644 --- a/demos/form-builder/lib/seed.ts +++ b/demos/form-builder/lib/seed.ts @@ -1,11 +1,6 @@ import type { Adapter } from "@btst/stack/plugins/api"; -let seeded = false; - export async function seedFormBuilderData(adapter: Adapter) { - if (seeded) return; - seeded = true; - try { const existing = await adapter.findMany({ model: "form", limit: 1 }); if (existing.length > 0) return; diff --git a/demos/form-builder/lib/stack.ts b/demos/form-builder/lib/stack.ts index 9c12c5b4..74690b13 100644 --- a/demos/form-builder/lib/stack.ts +++ b/demos/form-builder/lib/stack.ts @@ -4,8 +4,11 @@ import { formBuilderBackendPlugin } from "@btst/stack/plugins/form-builder/api"; import { openApiBackendPlugin } from "@btst/stack/plugins/open-api/api"; import { seedFormBuilderData } from "./seed"; +// Persist stack and seed promise on `global` so Next.js module re-evaluations +// (HMR, per-request server components) don't create a second instance or re-run the seed. const globalForStack = global as typeof global & { __btst_stack__?: ReturnType; + __btst_seeded__?: Promise; }; function createStack() { @@ -25,6 +28,8 @@ function createStack() { export const myStack = (globalForStack.__btst_stack__ ??= createStack()); -seedFormBuilderData(myStack.adapter).catch(console.error); +globalForStack.__btst_seeded__ ??= seedFormBuilderData(myStack.adapter).catch( + console.error, +); export const { handler, dbSchema } = myStack; diff --git a/demos/form-builder/package.json b/demos/form-builder/package.json index c9bcfe7d..dddcda1d 100644 --- a/demos/form-builder/package.json +++ b/demos/form-builder/package.json @@ -3,14 +3,14 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", - "build": "next build", + "dev": "node copy-stack-src.mjs && next dev", + "build": "node copy-stack-src.mjs && next build", "start": "next start" }, "dependencies": { "@ai-sdk/react": "^3.0.118", "@btst/adapter-memory": "^2.0.3", - "@btst/stack": "^2.5.1", + "@btst/stack": "^2.5.2", "@btst/yar": "^1.2.0", "@hookform/resolvers": "^5.2.2", "@radix-ui/react-checkbox": "^1.3.3", @@ -31,7 +31,7 @@ "highlight.js": "^11.11.1", "katex": "^0.16.35", "lucide-react": "^0.577.0", - "next": "16.1.6", + "next": "15.4.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.3", diff --git a/demos/kanban/.gitignore b/demos/kanban/.gitignore index 7fc49260..abb3b440 100644 --- a/demos/kanban/.gitignore +++ b/demos/kanban/.gitignore @@ -12,3 +12,5 @@ # misc .DS_Store *.pem +.btst-stack-src/ +.btst-stack-ui/ diff --git a/demos/kanban/app/globals.css b/demos/kanban/app/globals.css index 05473a81..f0d34577 100644 --- a/demos/kanban/app/globals.css +++ b/demos/kanban/app/globals.css @@ -2,6 +2,15 @@ @import "tw-animate-css"; @import "@btst/stack/plugins/kanban/css"; @import "@btst/stack/plugins/route-docs/css"; +/* WebContainers workaround: the WASM Tailwind scanner cannot traverse node_modules + * (https://github.com/tailwindlabs/tailwindcss/issues/18418). copy-stack-src.mjs + * (run via predev/prebuild) copies @btst/stack/src outside node_modules so Tailwind + * can scan it. Safe to remove once the upstream bug is fixed. */ +@source "./.btst-stack-src/**/*.{ts,tsx}"; +@source "./.btst-stack-ui/**/*.{ts,tsx}"; +/* Monorepo fallback: direct source path used in local development */ +@source "../../../packages/stack/src/**/*.{ts,tsx}"; +@source "../../../packages/ui/src/**/*.{ts,tsx}"; @custom-variant dark (&:is(.dark *)); diff --git a/demos/kanban/copy-stack-src.mjs b/demos/kanban/copy-stack-src.mjs new file mode 100644 index 00000000..dc457f07 --- /dev/null +++ b/demos/kanban/copy-stack-src.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * Copies @btst/stack/src outside of node_modules so Tailwind's WASM scanner + * (used in WebContainers/StackBlitz) can traverse it. + * + * The WASM oxide scanner cannot scan inside node_modules due to a bug: + * https://github.com/tailwindlabs/tailwindcss/issues/18418 + * + * Run automatically via the predev/prebuild npm lifecycle hooks. + */ +import { cp, mkdir, rm } from "fs/promises"; +import { existsSync } from "fs"; + +const src = "node_modules/@btst/stack/src"; +const dest = ".btst-stack-src"; + +const uiSrc = "node_modules/@btst/stack/dist/packages/ui"; +const uiDest = ".btst-stack-ui"; + +if (!existsSync(src)) { + // Likely running in the monorepo where the workspace symlink does not expose + // src/ — fall back gracefully; @source paths in globals.css cover this case. + console.log( + "[copy-stack-src] node_modules/@btst/stack/src not found, skipping", + ); + process.exit(0); +} + +await rm(dest, { recursive: true, force: true }); +await mkdir(dest, { recursive: true }); +await cp(src, dest, { recursive: true }); +console.log("[copy-stack-src] copied @btst/stack/src → .btst-stack-src"); + +if (existsSync(uiSrc)) { + await rm(uiDest, { recursive: true, force: true }); + await mkdir(uiDest, { recursive: true }); + await cp(uiSrc, uiDest, { recursive: true }); + console.log( + "[copy-stack-src] copied @btst/stack/dist/packages/ui → .btst-stack-ui", + ); +} else { + console.log( + "[copy-stack-src] node_modules/@btst/stack/dist/packages/ui not found, skipping", + ); +} diff --git a/demos/kanban/lib/seed.ts b/demos/kanban/lib/seed.ts index 7342f974..793aa4ea 100644 --- a/demos/kanban/lib/seed.ts +++ b/demos/kanban/lib/seed.ts @@ -5,12 +5,7 @@ import { } from "@btst/stack/plugins/kanban/api"; import type { Adapter } from "@btst/stack/plugins/api"; -let seeded = false; - export async function seedKanbanData(adapter: Adapter) { - if (seeded) return; - seeded = true; - try { const board = await findOrCreateKanbanBoard( adapter, diff --git a/demos/kanban/lib/stack.ts b/demos/kanban/lib/stack.ts index 77cd2fee..0afac7d3 100644 --- a/demos/kanban/lib/stack.ts +++ b/demos/kanban/lib/stack.ts @@ -4,8 +4,11 @@ import { kanbanBackendPlugin } from "@btst/stack/plugins/kanban/api"; import { openApiBackendPlugin } from "@btst/stack/plugins/open-api/api"; import { seedKanbanData } from "./seed"; +// Persist stack and seed promise on `global` so Next.js module re-evaluations +// (HMR, per-request server components) don't create a second instance or re-run the seed. const globalForStack = global as typeof global & { __btst_stack__?: ReturnType; + __btst_seeded__?: Promise; }; function createStack() { @@ -25,6 +28,8 @@ function createStack() { export const myStack = (globalForStack.__btst_stack__ ??= createStack()); -seedKanbanData(myStack.adapter).catch(console.error); +globalForStack.__btst_seeded__ ??= seedKanbanData(myStack.adapter).catch( + console.error, +); export const { handler, dbSchema } = myStack; diff --git a/demos/kanban/package.json b/demos/kanban/package.json index 26c02bb0..b2cac79c 100644 --- a/demos/kanban/package.json +++ b/demos/kanban/package.json @@ -3,14 +3,14 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", - "build": "next build", + "dev": "node copy-stack-src.mjs && next dev", + "build": "node copy-stack-src.mjs && next build", "start": "next start" }, "dependencies": { "@ai-sdk/react": "^3.0.118", "@btst/adapter-memory": "^2.0.3", - "@btst/stack": "^2.5.1", + "@btst/stack": "^2.5.2", "@btst/yar": "^1.2.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -30,7 +30,7 @@ "clsx": "^2.1.1", "date-fns": "^4.1.0", "lucide-react": "^0.577.0", - "next": "16.1.6", + "next": "15.4.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.3", diff --git a/demos/ui-builder/.gitignore b/demos/ui-builder/.gitignore index 7fc49260..abb3b440 100644 --- a/demos/ui-builder/.gitignore +++ b/demos/ui-builder/.gitignore @@ -12,3 +12,5 @@ # misc .DS_Store *.pem +.btst-stack-src/ +.btst-stack-ui/ diff --git a/demos/ui-builder/app/globals.css b/demos/ui-builder/app/globals.css index a9d201e4..eaf9e8f4 100644 --- a/demos/ui-builder/app/globals.css +++ b/demos/ui-builder/app/globals.css @@ -3,6 +3,15 @@ @import "@btst/stack/plugins/ui-builder/css"; @import "@btst/stack/plugins/cms/css"; @import "@btst/stack/plugins/route-docs/css"; +/* WebContainers workaround: the WASM Tailwind scanner cannot traverse node_modules + * (https://github.com/tailwindlabs/tailwindcss/issues/18418). copy-stack-src.mjs + * (run via predev/prebuild) copies @btst/stack/src outside node_modules so Tailwind + * can scan it. Safe to remove once the upstream bug is fixed. */ +@source "./.btst-stack-src/**/*.{ts,tsx}"; +@source "./.btst-stack-ui/**/*.{ts,tsx}"; +/* Monorepo fallback: direct source path used in local development */ +@source "../../../packages/stack/src/**/*.{ts,tsx}"; +@source "../../../packages/ui/src/**/*.{ts,tsx}"; @custom-variant dark (&:is(.dark *)); diff --git a/demos/ui-builder/copy-stack-src.mjs b/demos/ui-builder/copy-stack-src.mjs new file mode 100644 index 00000000..dc457f07 --- /dev/null +++ b/demos/ui-builder/copy-stack-src.mjs @@ -0,0 +1,45 @@ +#!/usr/bin/env node +/** + * Copies @btst/stack/src outside of node_modules so Tailwind's WASM scanner + * (used in WebContainers/StackBlitz) can traverse it. + * + * The WASM oxide scanner cannot scan inside node_modules due to a bug: + * https://github.com/tailwindlabs/tailwindcss/issues/18418 + * + * Run automatically via the predev/prebuild npm lifecycle hooks. + */ +import { cp, mkdir, rm } from "fs/promises"; +import { existsSync } from "fs"; + +const src = "node_modules/@btst/stack/src"; +const dest = ".btst-stack-src"; + +const uiSrc = "node_modules/@btst/stack/dist/packages/ui"; +const uiDest = ".btst-stack-ui"; + +if (!existsSync(src)) { + // Likely running in the monorepo where the workspace symlink does not expose + // src/ — fall back gracefully; @source paths in globals.css cover this case. + console.log( + "[copy-stack-src] node_modules/@btst/stack/src not found, skipping", + ); + process.exit(0); +} + +await rm(dest, { recursive: true, force: true }); +await mkdir(dest, { recursive: true }); +await cp(src, dest, { recursive: true }); +console.log("[copy-stack-src] copied @btst/stack/src → .btst-stack-src"); + +if (existsSync(uiSrc)) { + await rm(uiDest, { recursive: true, force: true }); + await mkdir(uiDest, { recursive: true }); + await cp(uiSrc, uiDest, { recursive: true }); + console.log( + "[copy-stack-src] copied @btst/stack/dist/packages/ui → .btst-stack-ui", + ); +} else { + console.log( + "[copy-stack-src] node_modules/@btst/stack/dist/packages/ui not found, skipping", + ); +} diff --git a/demos/ui-builder/lib/seed.ts b/demos/ui-builder/lib/seed.ts index d8461d25..039329e2 100644 --- a/demos/ui-builder/lib/seed.ts +++ b/demos/ui-builder/lib/seed.ts @@ -1,11 +1,6 @@ import { UI_BUILDER_TYPE_SLUG } from "@btst/stack/plugins/ui-builder"; -let seeded = false; - export async function seedUIBuilderData(api: any) { - if (seeded) return; - seeded = true; - try { // This also calls ensureSynced internally const existing = await api.cms.getAllContentItems(UI_BUILDER_TYPE_SLUG, { diff --git a/demos/ui-builder/lib/stack.ts b/demos/ui-builder/lib/stack.ts index c2664e89..36ca5eaf 100644 --- a/demos/ui-builder/lib/stack.ts +++ b/demos/ui-builder/lib/stack.ts @@ -5,8 +5,11 @@ import { openApiBackendPlugin } from "@btst/stack/plugins/open-api/api"; import { UI_BUILDER_CONTENT_TYPE } from "@btst/stack/plugins/ui-builder"; import { seedUIBuilderData } from "./seed"; +// Persist stack and seed promise on `global` so Next.js module re-evaluations +// (HMR, per-request server components) don't create a second instance or re-run the seed. const globalForStack = global as typeof global & { __btst_stack__?: ReturnType; + __btst_seeded__?: Promise; }; function createStack() { @@ -28,6 +31,8 @@ function createStack() { export const myStack = (globalForStack.__btst_stack__ ??= createStack()); -seedUIBuilderData(myStack.api).catch(console.error); +globalForStack.__btst_seeded__ ??= seedUIBuilderData(myStack.api).catch( + console.error, +); export const { handler, dbSchema } = myStack; diff --git a/demos/ui-builder/package.json b/demos/ui-builder/package.json index 23162df6..ca8d856b 100644 --- a/demos/ui-builder/package.json +++ b/demos/ui-builder/package.json @@ -3,14 +3,14 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev", - "build": "next build", + "dev": "node copy-stack-src.mjs && next dev", + "build": "node copy-stack-src.mjs && next build", "start": "next start" }, "dependencies": { "@ai-sdk/react": "^3.0.118", "@btst/adapter-memory": "^2.0.3", - "@btst/stack": "^2.5.1", + "@btst/stack": "^2.5.2", "@btst/yar": "^1.2.0", "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", @@ -46,7 +46,7 @@ "clsx": "^2.1.1", "date-fns": "^4.1.0", "lucide-react": "^0.577.0", - "next": "16.1.6", + "next": "15.4.1", "next-themes": "^0.4.6", "radix-ui": "^1.4.3", "react": "19.2.3", diff --git a/docs/content/docs/demos/ai-chat.mdx b/docs/content/docs/demos/ai-chat.mdx index a22efc91..c74ddb8d 100644 --- a/docs/content/docs/demos/ai-chat.mdx +++ b/docs/content/docs/demos/ai-chat.mdx @@ -15,4 +15,11 @@ A standalone Next.js demo showcasing the `ai-chat` plugin in **public mode** (no To enable AI responses in StackBlitz, add your `OPENAI_API_KEY` to the `.env.local` file (copy from `.env.local.example`). -[Open in CodeSandbox →](https://codesandbox.io/p/devbox/github/better-stack-ai/better-stack/tree/main/demos/ai-chat) +[Open in CodeSandbox →](https://codesandbox.io/p/devbox/github/better-stack-ai/better-stack/tree/main/demos/ai-chat?file=%2Flib%2Fstack.ts) +[Open in StackBlitz →](https://stackblitz.com/github/better-stack-ai/better-stack/tree/main/demos/ai-chat?startScript=dev&file=lib%2Fstack.ts) + +