@autono/open-pages 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/README.md +8 -5
  2. package/dist/{build-TP72kiw7.js → build-Cq6uszaI.js} +3 -3
  3. package/dist/cli/bin.js +4 -4
  4. package/dist/{config-L0IbktJ0.js → config-DniCVIl6.js} +34 -7
  5. package/dist/{dev-I2IyEh5W.js → dev-DyY9SvwF.js} +2 -2
  6. package/dist/export-DqLDEQkL.js +4 -0
  7. package/dist/{export-ic28osQP.js → export-pYjzc4Cl.js} +10 -3
  8. package/dist/{open-pages-plugin-D_DN_zjh.js → open-pages-plugin-CACQ7TVo.js} +40 -6
  9. package/dist/{preview-BrDuvgAN.js → preview-CLb35jHx.js} +2 -2
  10. package/dist/vite/index.js +2 -2
  11. package/package.json +1 -1
  12. package/skills/apply-comments/SKILL.md +4 -4
  13. package/skills/create-page/SKILL.md +26 -8
  14. package/skills/create-theme/SKILL.md +137 -150
  15. package/skills/current-page/SKILL.md +1 -1
  16. package/skills/page-authoring/SKILL.md +97 -42
  17. package/skills/page-authoring/references/interactivity.md +70 -22
  18. package/skills/page-authoring/references/layout-and-responsive.md +38 -17
  19. package/skills/page-authoring/references/typography-and-color.md +50 -32
  20. package/skills/shadcn/SKILL.md +277 -0
  21. package/skills/shadcn/assets/shadcn-small.png +0 -0
  22. package/skills/shadcn/assets/shadcn.png +0 -0
  23. package/skills/shadcn/cli.md +290 -0
  24. package/skills/shadcn/customization.md +209 -0
  25. package/skills/shadcn/mcp.md +105 -0
  26. package/skills/shadcn/registry.md +277 -0
  27. package/skills/shadcn/rules/base-vs-radix.md +306 -0
  28. package/skills/shadcn/rules/chat.md +224 -0
  29. package/skills/shadcn/rules/composition.md +213 -0
  30. package/skills/shadcn/rules/forms.md +192 -0
  31. package/skills/shadcn/rules/icons.md +101 -0
  32. package/skills/shadcn/rules/styling.md +185 -0
  33. package/src/app/components/asset-view.tsx +11 -11
  34. package/src/app/components/command/command-menu.tsx +6 -6
  35. package/src/app/components/command/command.tsx +2 -2
  36. package/src/app/components/command/home-command-menu.tsx +2 -2
  37. package/src/app/components/icon-tooltip.tsx +1 -1
  38. package/src/app/components/language-toggle.tsx +7 -7
  39. package/src/app/components/sidebar/folder-item.tsx +5 -5
  40. package/src/app/components/sidebar/icon-picker.tsx +3 -3
  41. package/src/app/components/sidebar/sidebar-footer.tsx +4 -4
  42. package/src/app/components/sidebar/sidebar.tsx +6 -6
  43. package/src/app/components/theme-toggle.tsx +6 -6
  44. package/src/app/components/themes/theme-detail.tsx +4 -4
  45. package/src/app/components/themes/themes-gallery.tsx +1 -1
  46. package/src/app/components/ui/badge.tsx +1 -1
  47. package/src/app/components/ui/button.tsx +1 -1
  48. package/src/app/components/ui/card.tsx +1 -1
  49. package/src/app/components/ui/context-menu.tsx +1 -1
  50. package/src/app/components/ui/dialog.tsx +2 -2
  51. package/src/app/components/ui/dropdown-menu.tsx +1 -1
  52. package/src/app/components/ui/input.tsx +1 -1
  53. package/src/app/components/ui/label.tsx +1 -1
  54. package/src/app/components/ui/popover.tsx +1 -1
  55. package/src/app/components/ui/progress.tsx +1 -1
  56. package/src/app/components/ui/scroll-area.tsx +1 -1
  57. package/src/app/components/ui/select.tsx +1 -1
  58. package/src/app/components/ui/separator.tsx +1 -1
  59. package/src/app/components/ui/slider.tsx +1 -1
  60. package/src/app/components/ui/tabs.tsx +1 -1
  61. package/src/app/components/ui/textarea.tsx +1 -1
  62. package/src/app/components/ui/toggle-group.tsx +2 -2
  63. package/src/app/components/ui/toggle.tsx +1 -1
  64. package/src/app/components/ui/tooltip.tsx +1 -1
  65. package/src/app/frame/main.tsx +3 -1
  66. package/src/app/lib/themes.ts +1 -0
  67. package/src/app/lib/use-restart-server.ts +1 -1
  68. package/src/app/routes/home-shell.tsx +7 -7
  69. package/src/app/routes/home.tsx +5 -5
  70. package/src/app/routes/page.tsx +2 -2
  71. package/src/app/routes/themes.tsx +1 -1
  72. package/src/app/virtual.d.ts +3 -0
  73. package/dist/export-B-kBRWB1.js +0 -4
package/README.md CHANGED
@@ -20,9 +20,10 @@ pnpm add @autono/open-pages react react-dom
20
20
 
21
21
  - **Dev server + workspace viewer** — a home page listing every page with live thumbnails, folders, search, and sort; a per-page viewer at `/p/<id>` with desktop / tablet (820px) / mobile (390px) viewport toggles, reload, and open-in-new-tab.
22
22
  - **Inspect mode** — press `i`, click any element on the page to see its exact source line and leave a comment. Comments persist as `@page-comment` markers in the source, ready for a coding agent to apply.
23
- - **Vite plugin** — discovers `pages/<id>/index.tsx` and `pages/<id>/index.html`, exposes pages via virtual modules, generates a Tailwind v4 stylesheet scoped to `pages/` and `themes/`, hot-reloads on add/remove/edit.
23
+ - **Vite plugin** — discovers `pages/<id>/index.tsx` and `pages/<id>/index.html`, exposes pages via virtual modules, serves the workspace's `styles/globals.css` (Tailwind v4 plus the shadcn tokens) as the page stylesheet, maps `@` to the workspace root, and hot-reloads on add/remove/edit.
24
+ - **shadcn/ui baked in** — a scaffolded workspace ships all 61 components under `ui/`, `lib/utils.ts`, `hooks/`, and a token theme in `styles/globals.css`. Pages import from `@/ui/*`; no `add` step. Themes are `themes/<id>.css` token overrides the runtime injects when a page sets `meta.theme`.
24
25
  - **Export CLI** — builds each page into `export/<id>/index.html` plus hashed assets with relative URLs.
25
- - **Agent skills** — file-based skills (`create-page`, `page-authoring`, `apply-comments`, `create-theme`, `current-page`) that sync into workspaces; no MCP server required.
26
+ - **Agent skills** — file-based skills (`create-page`, `page-authoring`, `apply-comments`, `create-theme`, `current-page`, and the official `shadcn` skill) that sync into workspaces; no MCP server required.
26
27
 
27
28
  ## CLI
28
29
 
@@ -39,10 +40,11 @@ Once installed, the `open-pages` bin is available in the workspace:
39
40
 
40
41
  ## Authoring
41
42
 
42
- A page is one folder under `pages/` with an `index.tsx` that default-exports a React component. Style with Tailwind via `className`; it is a real web page, so hooks, state, and browser APIs all work.
43
+ A page is one folder under `pages/` with an `index.tsx` that default-exports a React component. Compose from the bundled shadcn components, style with Tailwind via `className`; it is a real web page, so hooks, state, and browser APIs all work.
43
44
 
44
45
  ```tsx
45
46
  import type { PageMeta } from '@autono/open-pages';
47
+ import { Button } from '@/ui/button';
46
48
 
47
49
  export const meta: PageMeta = {
48
50
  title: 'Hello, open-pages',
@@ -51,9 +53,10 @@ export const meta: PageMeta = {
51
53
 
52
54
  export default function Page() {
53
55
  return (
54
- <main className="mx-auto max-w-2xl px-6 py-24">
56
+ <main className="min-h-screen bg-background px-6 py-24 text-foreground">
55
57
  <h1 className="text-5xl font-bold tracking-tight">Hello, open-pages</h1>
56
- <p className="mt-4 text-lg text-slate-600">This is a real web page.</p>
58
+ <p className="mt-4 text-lg text-muted-foreground">This is a real web page.</p>
59
+ <Button className="mt-8">Get started</Button>
57
60
  </main>
58
61
  );
59
62
  }
@@ -1,9 +1,9 @@
1
1
  import "./design-C13iz9_4.js";
2
- import { findPages, loadUserConfig } from "./open-pages-plugin-D_DN_zjh.js";
3
- import { createViteConfig } from "./config-L0IbktJ0.js";
2
+ import { findPages, loadUserConfig } from "./open-pages-plugin-CACQ7TVo.js";
3
+ import { createViteConfig } from "./config-DniCVIl6.js";
4
4
  import "./update-package-Cxt57pz7.js";
5
5
  import { readCoreVersion } from "./version-B6AiiGvT.js";
6
- import { buildPage } from "./export-ic28osQP.js";
6
+ import { buildPage } from "./export-pYjzc4Cl.js";
7
7
  import path from "node:path";
8
8
  import { build as build$1, mergeConfig } from "vite";
9
9
 
package/dist/cli/bin.js CHANGED
@@ -57,19 +57,19 @@ async function run(argv) {
57
57
  program.name("open-pages").description("Author web pages — we handle the Vite/React stack.").version(version, "-v, --version", "print version").helpOption("-h, --help", "show help").showHelpAfterError(chalk.dim("(run `open-pages --help` for usage)"));
58
58
  program.command("dev").description("Start the dev server").addOption(new Option("-p, --port <port>", "port to listen on").argParser(parsePort)).addOption(new Option("--host [host]", "expose on the network (optional host)")).option("--open", "open the browser on start").option("--no-skills-check", "skip the built-in skills drift check").action(async (flags) => {
59
59
  if (flags.skillsCheck !== false) await runSkillsDriftCheck(resolveBuiltinSkillsDir());
60
- const { dev } = await import("../dev-I2IyEh5W.js");
60
+ const { dev } = await import("../dev-DyY9SvwF.js");
61
61
  await dev(flags);
62
62
  });
63
63
  program.command("build").description("Build the workspace as a static site").option("--out-dir <dir>", "output directory (defaults to `dist`)").action(async (flags) => {
64
- const { build } = await import("../build-TP72kiw7.js");
64
+ const { build } = await import("../build-Cq6uszaI.js");
65
65
  await build(flags);
66
66
  });
67
67
  program.command("preview").description("Preview the production build").addOption(new Option("-p, --port <port>", "port to listen on").argParser(parsePort)).addOption(new Option("--host [host]", "expose on the network (optional host)")).option("--open", "open the browser on start").action(async (flags) => {
68
- const { preview } = await import("../preview-BrDuvgAN.js");
68
+ const { preview } = await import("../preview-CLb35jHx.js");
69
69
  await preview(flags);
70
70
  });
71
71
  program.command("export").description("Build pages into self-contained static folders (one per page)").argument("[pages...]", "page ids to export (default: all)").option("--out-dir <dir>", "output directory (defaults to `export`)").action(async (pages, flags) => {
72
- const { exportPages } = await import("../export-B-kBRWB1.js");
72
+ const { exportPages } = await import("../export-DqLDEQkL.js");
73
73
  await exportPages({
74
74
  pages,
75
75
  outDir: flags.outDir
@@ -1,5 +1,5 @@
1
1
  import { defaultDesign } from "./design-C13iz9_4.js";
2
- import { PAGE_ID_RE, duplicatePageDir, loadUserConfig, openPagesPlugin, resolvePageEntry, rmPageDir, updateMetaTitleInSource, validatePageName } from "./open-pages-plugin-D_DN_zjh.js";
2
+ import { PAGE_ID_RE, duplicatePageDir, invalidatePagesModule, loadUserConfig, openPagesPlugin, resolvePageEntry, rmPageDir, updateMetaTitleInSource, validatePageName } from "./open-pages-plugin-CACQ7TVo.js";
3
3
  import { detectPackageManager, fetchLatest, formatCommand, invalidateLatestCache, isOutdated, localOpenPagesCommand, runCommand, updateCommandFor } from "./update-package-Cxt57pz7.js";
4
4
  import { APP_ROOT, PKG_ROOT, readCoreVersion } from "./version-B6AiiGvT.js";
5
5
  import fs from "node:fs/promises";
@@ -2131,6 +2131,7 @@ function registerPageRoutes(server, ctx) {
2131
2131
  manifest.assignments[duplicated.pageId] = folderId;
2132
2132
  await writeManifest(ctx.manifestPath, manifest);
2133
2133
  }
2134
+ invalidatePagesModule(server);
2134
2135
  return json(res, 200, {
2135
2136
  ok: true,
2136
2137
  pageId: duplicated.pageId
@@ -2172,6 +2173,7 @@ function registerPageRoutes(server, ctx) {
2172
2173
  const manifest = await readManifest(ctx.manifestPath);
2173
2174
  delete manifest.assignments[pageId];
2174
2175
  await writeManifest(ctx.manifestPath, manifest);
2176
+ invalidatePagesModule(server);
2175
2177
  return json(res, 200, { ok: true });
2176
2178
  }
2177
2179
  return next();
@@ -2826,10 +2828,14 @@ function designPlugin(opts) {
2826
2828
 
2827
2829
  //#endregion
2828
2830
  //#region src/vite/loc-tags-plugin.ts
2829
- const FORWARDING_COMPONENTS = new Set(["ImagePlaceholder"]);
2831
+ const UNTAGGABLE_COMPONENTS = new Set([
2832
+ "Fragment",
2833
+ "StrictMode",
2834
+ "Suspense"
2835
+ ]);
2830
2836
  function isTaggableJsxName(name) {
2831
2837
  if (!t.isJSXIdentifier(name)) return false;
2832
- return /^[a-z]/.test(name.name) || FORWARDING_COMPONENTS.has(name.name);
2838
+ return !UNTAGGABLE_COMPONENTS.has(name.name);
2833
2839
  }
2834
2840
  function alreadyTagged(opening) {
2835
2841
  return opening.attributes.some((attr) => t.isJSXAttribute(attr) && t.isJSXIdentifier(attr.name) && attr.name.name === "data-op-loc");
@@ -2943,11 +2949,14 @@ async function readTheme(mdAbs, themesRoot) {
2943
2949
  break;
2944
2950
  }
2945
2951
  }
2952
+ const cssPath = path.join(themesRoot, `${id}.css`);
2953
+ const cssAbs = existsSync(cssPath) ? cssPath : null;
2946
2954
  return {
2947
2955
  id,
2948
2956
  frontmatter: fm,
2949
2957
  body,
2950
- demoAbs
2958
+ demoAbs,
2959
+ cssAbs
2951
2960
  };
2952
2961
  }
2953
2962
  function generateThemesModule(themes, isDev) {
@@ -2956,7 +2965,8 @@ function generateThemesModule(themes, isDev) {
2956
2965
  name: t$4.frontmatter.name,
2957
2966
  description: t$4.frontmatter.description,
2958
2967
  body: t$4.body,
2959
- hasDemo: t$4.demoAbs !== null
2968
+ hasDemo: t$4.demoAbs !== null,
2969
+ hasCss: t$4.cssAbs !== null
2960
2970
  }));
2961
2971
  const cases = themes.flatMap((t$4) => {
2962
2972
  const abs = t$4.demoAbs;
@@ -2965,8 +2975,24 @@ function generateThemesModule(themes, isDev) {
2965
2975
  const importExpr = isDev ? `import(/* @vite-ignore */ import.meta.env.BASE_URL + ${JSON.stringify(importPath)})` : `import(${JSON.stringify(importPath)})`;
2966
2976
  return [` case ${JSON.stringify(t$4.id)}: return ${importExpr};`];
2967
2977
  }).join("\n");
2978
+ const withCss = themes.filter((t$4) => t$4.cssAbs !== null);
2979
+ const cssCases = withCss.map((t$4) => {
2980
+ const importPath = isDev ? `@fs/${normalizePath(t$4.cssAbs).replace(/^\/+/, "")}` : t$4.cssAbs;
2981
+ const importExpr = isDev ? `import(/* @vite-ignore */ import.meta.env.BASE_URL + ${JSON.stringify(importPath)})` : `import(${JSON.stringify(importPath)})`;
2982
+ return ` case ${JSON.stringify(t$4.id)}: return ${importExpr};`;
2983
+ }).join("\n");
2968
2984
  return `// virtual:open-pages/themes — generated
2969
2985
  export const themes = ${JSON.stringify(meta)};
2986
+ export const themeCssIds = ${JSON.stringify(withCss.map((t$4) => t$4.id))};
2987
+
2988
+ // Importing a stylesheet through Vite injects it into the document; the
2989
+ // frame calls this for a page's meta.theme and for theme demos.
2990
+ export async function loadThemeCss(id) {
2991
+ switch (id) {
2992
+ ${cssCases}
2993
+ default: return;
2994
+ }
2995
+ }
2970
2996
 
2971
2997
  export async function loadThemeDemo(id) {
2972
2998
  switch (id) {
@@ -3001,7 +3027,7 @@ function themesPlugin(opts) {
3001
3027
  const rel = path.relative(themesRoot, p);
3002
3028
  if (rel.startsWith("..") || path.isAbsolute(rel)) return false;
3003
3029
  if (rel.includes(path.sep)) return false;
3004
- return /\.(md|demo\.(tsx|jsx|ts|js))$/.test(rel);
3030
+ return /\.(md|css|demo\.(tsx|jsx|ts|js))$/.test(rel);
3005
3031
  };
3006
3032
  let reloadTimer = null;
3007
3033
  const reload = () => {
@@ -3073,7 +3099,8 @@ async function createViteConfig(opts) {
3073
3099
  })
3074
3100
  ],
3075
3101
  resolve: { alias: {
3076
- "@": APP_ROOT,
3102
+ "~": APP_ROOT,
3103
+ "@": userCwd,
3077
3104
  "@assets": assetsAbs
3078
3105
  } },
3079
3106
  optimizeDeps: {
@@ -1,6 +1,6 @@
1
1
  import "./design-C13iz9_4.js";
2
- import "./open-pages-plugin-D_DN_zjh.js";
3
- import { DEV_SUPERVISED_ENV, RESTART_EXIT_CODE, createViteConfig } from "./config-L0IbktJ0.js";
2
+ import "./open-pages-plugin-CACQ7TVo.js";
3
+ import { DEV_SUPERVISED_ENV, RESTART_EXIT_CODE, createViteConfig } from "./config-DniCVIl6.js";
4
4
  import "./update-package-Cxt57pz7.js";
5
5
  import "./version-B6AiiGvT.js";
6
6
  import chalk from "chalk";
@@ -0,0 +1,4 @@
1
+ import "./open-pages-plugin-CACQ7TVo.js";
2
+ import { buildPage, exportPages } from "./export-pYjzc4Cl.js";
3
+
4
+ export { exportPages };
@@ -1,7 +1,8 @@
1
- import { extractMeta, findPages, loadUserConfig, openPagesPlugin } from "./open-pages-plugin-D_DN_zjh.js";
1
+ import { extractMeta, findPages, loadUserConfig, openPagesPlugin } from "./open-pages-plugin-CACQ7TVo.js";
2
2
  import chalk from "chalk";
3
3
  import { mkdir, readFile, realpath, rm, writeFile } from "node:fs/promises";
4
4
  import path from "node:path";
5
+ import { existsSync } from "node:fs";
5
6
  import tailwindcss from "@tailwindcss/vite";
6
7
  import react from "@vitejs/plugin-react";
7
8
  import { build } from "vite";
@@ -33,6 +34,8 @@ async function buildPage(opts) {
33
34
  const meta = extractMeta(await readFile(entry.file, "utf8"));
34
35
  const title = escapeHtml(meta.title ?? entry.id);
35
36
  const description = extractDescription(await readFile(entry.file, "utf8"));
37
+ const themeCss = meta.theme ? path.resolve(userCwd, config.themesDir ?? "themes", `${meta.theme}.css`) : null;
38
+ const themeImport = themeCss && existsSync(themeCss) ? `import ${JSON.stringify(themeCss)};` : "";
36
39
  await writeFile(path.join(root, "index.html"), [
37
40
  "<!doctype html>",
38
41
  "<html lang=\"en\">",
@@ -51,6 +54,7 @@ async function buildPage(opts) {
51
54
  ].filter((l) => l !== "").join("\n"), "utf8");
52
55
  await writeFile(path.join(root, "entry.tsx"), [
53
56
  "import 'virtual:open-pages/pages.css';",
57
+ themeImport,
54
58
  "import { StrictMode } from 'react';",
55
59
  "import { createRoot } from 'react-dom/client';",
56
60
  `import Page from ${JSON.stringify(entry.file)};`,
@@ -61,7 +65,7 @@ async function buildPage(opts) {
61
65
  " </StrictMode>,",
62
66
  ");",
63
67
  ""
64
- ].join("\n"), "utf8");
68
+ ].filter((l) => l !== "").join("\n"), "utf8");
65
69
  }
66
70
  await build({
67
71
  root,
@@ -78,7 +82,10 @@ async function buildPage(opts) {
78
82
  coreVersion
79
83
  })
80
84
  ],
81
- resolve: { alias: { "@assets": assetsAbs } },
85
+ resolve: { alias: {
86
+ "@": userCwd,
87
+ "@assets": assetsAbs
88
+ } },
82
89
  build: {
83
90
  outDir,
84
91
  emptyOutDir: true,
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
- import { existsSync, mkdirSync, writeFileSync } from "node:fs";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
4
4
  import { loadConfigFromFile, normalizePath } from "vite";
5
5
  import { parse } from "@babel/parser";
6
6
  import { createRequire } from "node:module";
@@ -267,6 +267,11 @@ const PAGES_VMOD = "virtual:open-pages/pages";
267
267
  const CONFIG_VMOD = "virtual:open-pages/config";
268
268
  const FOLDERS_VMOD = "virtual:open-pages/folders";
269
269
  const PAGES_CSS_VMOD = "virtual:open-pages/pages.css";
270
+ const WORKSPACE_SOURCE_DIRS = [
271
+ "ui",
272
+ "lib",
273
+ "hooks"
274
+ ];
270
275
  const ENTRY_GLOB = "*/index.{tsx,jsx,ts,js,html}";
271
276
  const REACT_ENTRY_RE = /^index\.(tsx|jsx|ts|js)$/;
272
277
  async function readFoldersManifest(file) {
@@ -288,6 +293,11 @@ async function readFoldersManifest(file) {
288
293
  function resolved(id) {
289
294
  return `\0${id}`;
290
295
  }
296
+ function invalidatePagesModule(server) {
297
+ const mod = server.moduleGraph.getModuleById(resolved(PAGES_VMOD));
298
+ if (mod) server.moduleGraph.invalidateModule(mod);
299
+ server.ws.send({ type: "full-reload" });
300
+ }
291
301
  /**
292
302
  * Every `pages/<id>/index.*` entry. A folder with both a React entry and an
293
303
  * `index.html` is a React page — the HTML is then a plain asset of the page.
@@ -443,6 +453,21 @@ ${cases}
443
453
  function pagesCssFile(userCwd) {
444
454
  return path.join(userCwd, "node_modules", ".open-pages", "pages.css");
445
455
  }
456
+ /**
457
+ * The workspace's own stylesheet when it has one (the shadcn `tailwind.css`
458
+ * entry from components.json, default `styles/globals.css`) — it carries the
459
+ * theme tokens every installed component reads. Falls back to the generated
460
+ * file for workspaces scaffolded without one.
461
+ */
462
+ function workspaceCssFile(userCwd) {
463
+ let rel = "styles/globals.css";
464
+ try {
465
+ const cfg = JSON.parse(readFileSync(path.join(userCwd, "components.json"), "utf8"));
466
+ if (typeof cfg.tailwind?.css === "string" && cfg.tailwind.css) rel = cfg.tailwind.css;
467
+ } catch {}
468
+ const abs = path.resolve(userCwd, rel);
469
+ return existsSync(abs) ? abs : null;
470
+ }
446
471
  const TAILWIND_CSS = normalizePath(createRequire(import.meta.url).resolve("tailwindcss/index.css"));
447
472
  function writePagesCss(userCwd, sources) {
448
473
  const file = pagesCssFile(userCwd);
@@ -502,7 +527,11 @@ function openPagesPlugin(opts) {
502
527
  name: "open-pages",
503
528
  config(_c, env) {
504
529
  isDev = env.command === "serve";
505
- cssFile = writePagesCss(userCwd, [pagesRoot, themesRoot]);
530
+ cssFile = workspaceCssFile(userCwd) ?? writePagesCss(userCwd, [
531
+ pagesRoot,
532
+ themesRoot,
533
+ ...WORKSPACE_SOURCE_DIRS.map((d) => path.join(userCwd, d))
534
+ ]);
506
535
  return { server: { fs: { allow: [userCwd] } } };
507
536
  },
508
537
  resolveId(id) {
@@ -557,11 +586,10 @@ function openPagesPlugin(opts) {
557
586
  if (reloadTimer) clearTimeout(reloadTimer);
558
587
  reloadTimer = setTimeout(() => {
559
588
  reloadTimer = null;
560
- const mod = server.moduleGraph.getModuleById(resolved(PAGES_VMOD));
561
- if (mod) server.moduleGraph.invalidateModule(mod);
562
- server.ws.send({ type: "full-reload" });
589
+ invalidatePagesModule(server);
563
590
  }, 150);
564
591
  };
592
+ const isPageDir = (p) => path.dirname(p) === pagesRoot;
565
593
  if (existsSync(pagesRoot)) server.watcher.add(pagesRoot);
566
594
  server.watcher.on("add", (p) => {
567
595
  if (pageIdForEntry(p)) reload();
@@ -569,6 +597,12 @@ function openPagesPlugin(opts) {
569
597
  server.watcher.on("unlink", (p) => {
570
598
  if (pageIdForEntry(p)) reload();
571
599
  });
600
+ server.watcher.on("addDir", (p) => {
601
+ if (isPageDir(p)) reload();
602
+ });
603
+ server.watcher.on("unlinkDir", (p) => {
604
+ if (isPageDir(p)) reload();
605
+ });
572
606
  server.watcher.on("change", (p) => {
573
607
  const entry = pageIdForEntry(p);
574
608
  if (entry?.kind === "html") queuePageChanged(server, entry.id);
@@ -629,4 +663,4 @@ async function loadUserConfig(userCwd) {
629
663
  }
630
664
 
631
665
  //#endregion
632
- export { PAGE_ID_RE, duplicatePageDir, extractMeta, findPages, loadUserConfig, openPagesPlugin, resolvePageEntry, rmPageDir, updateMetaTitleInSource, validatePageName };
666
+ export { PAGE_ID_RE, duplicatePageDir, extractMeta, findPages, invalidatePagesModule, loadUserConfig, openPagesPlugin, resolvePageEntry, rmPageDir, updateMetaTitleInSource, validatePageName };
@@ -1,6 +1,6 @@
1
1
  import "./design-C13iz9_4.js";
2
- import "./open-pages-plugin-D_DN_zjh.js";
3
- import { createViteConfig } from "./config-L0IbktJ0.js";
2
+ import "./open-pages-plugin-CACQ7TVo.js";
3
+ import { createViteConfig } from "./config-DniCVIl6.js";
4
4
  import "./update-package-Cxt57pz7.js";
5
5
  import "./version-B6AiiGvT.js";
6
6
  import { mergeConfig, preview as preview$1 } from "vite";
@@ -1,6 +1,6 @@
1
1
  import "../design-C13iz9_4.js";
2
- import "../open-pages-plugin-D_DN_zjh.js";
3
- import { createViteConfig } from "../config-L0IbktJ0.js";
2
+ import "../open-pages-plugin-CACQ7TVo.js";
3
+ import { createViteConfig } from "../config-DniCVIl6.js";
4
4
  import "../update-package-Cxt57pz7.js";
5
5
  import "../version-B6AiiGvT.js";
6
6
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@autono/open-pages",
3
- "version": "0.1.0",
3
+ "version": "0.3.0",
4
4
  "description": "Runtime and CLI for open-pages — live preview of React and HTML pages, click-to-comment inspector, and static export.",
5
5
  "type": "module",
6
6
  "exports": {
@@ -9,7 +9,7 @@ The open-pages viewer has an inspector that lets the user click any element on t
9
9
 
10
10
  Your job: read those markers, perform the described edits, and delete the markers.
11
11
 
12
- > **Before making any page edit**, consult the **`page-authoring`** skill — it is the technical reference for how a page is structured (file contract, `className` styling, layout and responsive rules, type scale, interactivity). A comment like *"make this bigger"* or *"change the accent colour"* should be applied in a way that stays consistent with those rules and still works on mobile.
12
+ > **Before making any page edit**, consult the **`page-authoring`** skill (and the **`shadcn`** skill for component props and variants) — it is the technical reference for how a page is structured (file contract, `className` styling, layout and responsive rules, type scale, interactivity). A comment like *"make this bigger"* or *"change the accent colour"* should be applied in a way that stays consistent with those rules and still works on mobile.
13
13
 
14
14
  ## Marker format
15
15
 
@@ -38,8 +38,8 @@ Your job: read those markers, perform the described edits, and delete the marker
38
38
  - If there are no markers, tell the user and stop.
39
39
 
40
40
  3. **Understand each comment in context.**
41
- - The targeted JSX element is the **enclosing** element of the marker — i.e. read upward from the marker line until you reach the unclosed JSX opening tag whose body the marker lives in. That element is the target. (For self-closing elements like `<img />`, the inspector hoists the marker to the nearest non-self-closing ancestor; in that case the comment usually refers to a child of the enclosing element rather than the enclosing element itself — use the `note` text to disambiguate.)
42
- - Read enough surrounding code (parent element, sibling elements, `className` strings, any state the element depends on) to apply the change faithfully. A comment inside a `<button>` with an `onClick` may be about behaviour, not looks.
41
+ - The targeted JSX element is the **enclosing** element of the marker — i.e. read upward from the marker line until you reach the unclosed JSX opening tag whose body the marker lives in. That element is the target. It is often a shadcn component (`<Button>`, `<Card>`, `<TabsTrigger>`): the inspector tags components too, so a click on the rendered button lands on the `<Button>` line in the page, never inside `ui/*.tsx`. (For self-closing elements like `<img />`, the inspector hoists the marker to the nearest non-self-closing ancestor; in that case the comment usually refers to a child of the enclosing element rather than the enclosing element itself — use the `note` text to disambiguate.)
42
+ - Read enough surrounding code (parent element, sibling elements, `className` strings, any state the element depends on) to apply the change faithfully. A comment inside a `<Button>` with an `onClick` may be about behaviour, not looks. Apply look changes with the component's variants and semantic tokens (`variant="outline"`, `bg-muted`), not raw colors; if the comment really wants a different palette across the page, say so — that is a theme change, not a page edit.
43
43
  - If the marker sits inside a `.map` body, the comment applies to the row template — every rendered row changes. If the user clearly meant one item ("make the *middle* one green"), change the data or add a per-item field rather than special-casing the JSX.
44
44
  - If the `note` is ambiguous, do the smallest reasonable interpretation and mention the assumption in your summary.
45
45
 
@@ -81,6 +81,6 @@ You can run this inline via `node -e '...'` if you need to inspect a payload; ot
81
81
 
82
82
  ## Do not
83
83
 
84
- - Do not touch `package.json`, `open-pages.config.ts`, or files outside `pages/`.
84
+ - Do not touch `package.json`, `open-pages.config.ts`, `ui/`, `lib/`, `hooks/`, `styles/globals.css`, or any file outside `pages/`. A comment that asks to change a shared component ("make all buttons rounder") is applied by wrapping in the page, or flagged as a theme/`ui/` change for the user to decide.
85
85
  - Do not add dependencies.
86
86
  - Do not re-introduce markers or leave `TODO` breadcrumbs — the user already has a record in git.
@@ -5,7 +5,9 @@ description: Use this skill when the user wants to create, build, draft, or gene
5
5
 
6
6
  # Create a page in open-pages
7
7
 
8
- This skill owns the **workflow** for building a new page. The technical reference — file contract, Tailwind via `className`, layout and responsive rules, type scale, interactivity, assets — lives in the **`page-authoring`** skill. Read that skill whenever you need details on *how* a page is structured. This skill assumes you'll consult it before writing code.
8
+ This skill owns the **workflow** for building a new page. The technical reference — file contract, composing from the preinstalled shadcn/ui set under `ui/`, semantic tokens, layout and responsive rules, type scale, interactivity, assets — lives in the **`page-authoring`** skill. Read that skill whenever you need details on *how* a page is structured. This skill assumes you'll consult it before writing code. Component-level questions (props, variants, blocks, registries) go to the vendored **`shadcn`** skill.
9
+
10
+ Every workspace already ships the full shadcn/ui set (`ui/`), `lib/utils.ts`, `hooks/`, and `styles/globals.css` with the neutral OKLCH tokens. Nothing needs `npx shadcn add`; pages import `@/ui/<name>` and compose.
9
11
 
10
12
  You only write files under `pages/<id>/`. Never modify `package.json`, `open-pages.config.ts`, or existing pages.
11
13
 
@@ -13,8 +15,8 @@ You only write files under `pages/<id>/`. Never modify `package.json`, `open-pag
13
15
 
14
16
  List files under `themes/`. If any theme markdown files exist (anything other than `README.md`), call `AskUserQuestion` with each theme id as an option plus a final **"no theme — design from scratch"** option. (`AskUserQuestion` holds at most 4 options — with 4+ themes, offer the 3 most topic-relevant plus "no theme"; the auto-added "Other" lets the user name any omitted theme.)
15
17
 
16
- - If the user picks a theme: read `themes/<id>.md` end-to-end. The theme's palette, typography, and fixed components are now authoritative — copy them directly into the page. **Also set `theme: '<theme-id>'` on the `meta` export** so the page back-links to the theme. In Step 2, skip the **visual direction** question (the theme already commits to one); confirm the page's purpose itself before moving on. Sections and interactivity are independent of theme — ask those normally.
17
- - If the user picks "no theme", or `themes/` contains no theme markdown files: proceed to Step 2 unchanged.
18
+ - If the user picks a theme: read `themes/<id>.md` end-to-end. Its direction, fonts, and component notes are now authoritative. **Set `theme: '<theme-id>'` on the `meta` export** — the runtime injects `themes/<id>.css` (the token overrides) into the page, so you keep writing semantic token classes and never paste color values. In Step 2, skip the **visual direction** question (the theme already commits to one); confirm the page's purpose itself before moving on. Sections and interactivity are independent of theme — ask those normally.
19
+ - If the user picks "no theme", or `themes/` contains no theme markdown files: the page uses the default neutral tokens from `styles/globals.css`. Proceed to Step 2 unchanged.
18
20
 
19
21
  If you skip the visual-direction question because a theme was picked, restate the theme name in Step 2 so the user can correct course before you start writing.
20
22
 
@@ -67,17 +69,32 @@ Sketch the page as an ordered list of sections before writing code. Common shape
67
69
 
68
70
  Decide the data shape now: which sections render from a typed const array (`.map`), which are explicit component instances — `page-authoring` explains why this matters for the inspector.
69
71
 
70
- ## Step 5 — Commit to a visual direction
72
+ ## Step 5 — Compose from `ui/`
73
+
74
+ Before writing code, map every planned section to the shadcn components it should be built from. Pages compose `@/ui/*` first and hand-roll only what shadcn lacks (heroes, marketing feature grids, footers). Typical mappings:
75
+
76
+ | Page type | Reach for |
77
+ | --- | --- |
78
+ | Landing / marketing | `navigation-menu` or plain links + `sheet` for mobile nav, `button` (CTAs), `badge` (eyebrows), `card` (features, pricing tiers), `tabs` or `switch` (billing toggle), `accordion` (FAQ), `separator` |
79
+ | Dashboard / app UI | `sidebar` (`SidebarProvider` + `SidebarInset`), `table`, `chart`, `card` (KPIs), `tabs`, `select`, `dropdown-menu`, `badge` (status), `skeleton`, `empty` |
80
+ | Form / signup | `form` (react-hook-form + zod) or `field`, `input`, `select`, `checkbox`, `radio-group`, `switch`, `textarea`, `button`, `alert` |
81
+ | Docs / long-form | `sidebar` or a sticky `scroll-area` TOC, `breadcrumb`, prose in a `max-w-[65ch]` column, `kbd`, `table`, `alert` (callouts) |
82
+ | Portfolio / gallery | `carousel`, `aspect-ratio`, `card`, `dialog` (lightbox), `hover-card` |
83
+ | Internal tool | `command` (palette), `combobox`, `resizable`, `context-menu`, `tooltip`, `sonner` (toasts), `alert-dialog` (confirm) |
84
+
85
+ Write the list down (component per section) so the structure in Step 4 and the code in Step 6 agree. If a section needs something shadcn only ships as a block (`dashboard-01`, `login-03`), the `shadcn` skill explains `npx shadcn@latest view` / `add` for blocks.
86
+
87
+ ## Step 5b — Commit to a visual direction
71
88
 
72
- One palette, one type scale, held for the whole page. The constraints (web type scale, palette structure, contrast) live in `page-authoring` and its `references/typography-and-color.md` — apply them. Define the palette as repeated Tailwind utilities (or a small const map of class strings). Fonts: default to the system stack; load a Google Font or self-hosted file only when the user names one or a theme requires it (`references/assets-and-fonts.md`).
89
+ One type scale, held for the whole page, expressed in the semantic tokens (`bg-background`, `bg-card`, `bg-primary`, `text-muted-foreground`, `border-border`) so a theme can repaint everything. Dark direction = `dark` on the root element, not a hand-picked dark palette. The constraints (web type scale, token system, contrast) live in `page-authoring` and its `references/typography-and-color.md` — apply them. Raw palette classes are for one deliberate brand moment, if any. Fonts: default to the token font; load a Google Font or self-hosted file only when the user names one or a theme requires it (`references/assets-and-fonts.md`).
73
90
 
74
91
  ## Step 6 — Write `pages/<id>/index.tsx`
75
92
 
76
- Read the **`page-authoring`** skill before writing — file contract, `className` styling, layout and responsive rules, interactivity constraints, assets. Its file-contract example is the starter template. Split large pages into `pages/<id>/components/*.tsx` when a section is more than ~80 lines.
93
+ Read the **`page-authoring`** skill before writing — file contract, `@/ui/*` composition, token styling, layout and responsive rules, interactivity constraints, assets. Its file-contract example is the starter template. Never edit files under `ui/`, `lib/`, `hooks/`, or `styles/` for a page; wrap components under `pages/<id>/components/` when they need a page-specific look. Split large pages into `pages/<id>/components/*.tsx` when a section is more than ~80 lines.
77
94
 
78
95
  ## Step 7 — Self-review
79
96
 
80
- Run the checklist in `page-authoring` ("Self-review before finishing"). Check all three viewports.
97
+ Run the checklist in `page-authoring` ("Self-review before finishing"): every button/input/card/dialog/tab is a `@/ui` component, colors are tokens, nothing under `ui/` changed. Check all three viewports.
81
98
 
82
99
  ## Step 8 — Hand off to the user
83
100
 
@@ -85,7 +102,8 @@ Tell the user:
85
102
 
86
103
  - The page id and file path you created.
87
104
  - The preview URL — `http://localhost:5173/p/<id>` — hot-reloads on every edit, with Desktop / Tablet / Mobile toggles and **Open** to view the page by itself.
88
- - That they can hit **Inspect** (or `i`) in the preview, click any element, and leave comments — then ask you to run `apply-comments`.
105
+ - That they can hit **Inspect** (or `i`) in the preview, click any element or component, and leave comments — then ask you to run `apply-comments`.
106
+ - That the look is token-driven: `/create-theme` can restyle the whole page (and every other page) without touching its code.
89
107
  - That `open-pages export <id>` writes `export/<id>/` — a static folder (index.html + assets) they can deploy to Netlify, Vercel, Cloudflare Pages, GitHub Pages, or any static host.
90
108
  - If dev isn't running: run the project's `dev` script from the project root with its package manager (`npm run dev`, `pnpm dev`, … — match the lockfile).
91
109