@autono/open-pages 0.1.0 → 0.4.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.
- package/README.md +8 -5
- package/dist/{build-TP72kiw7.js → build-L50pau9v.js} +3 -3
- package/dist/cli/bin.js +4 -4
- package/dist/{config-L0IbktJ0.js → config-CDUdFE8N.js} +34 -7
- package/dist/{dev-I2IyEh5W.js → dev-CzJYXFOO.js} +2 -2
- package/dist/export-BsK6OCpc.js +4 -0
- package/dist/{export-ic28osQP.js → export-DbVwSmKW.js} +10 -3
- package/dist/{open-pages-plugin-D_DN_zjh.js → open-pages-plugin-C0poXLsx.js} +52 -7
- package/dist/{preview-BrDuvgAN.js → preview-CPufRmuj.js} +2 -2
- package/dist/vite/index.js +2 -2
- package/package.json +6 -6
- package/skills/apply-comments/SKILL.md +4 -4
- package/skills/create-page/SKILL.md +26 -8
- package/skills/create-theme/SKILL.md +137 -150
- package/skills/current-page/SKILL.md +1 -1
- package/skills/page-authoring/SKILL.md +97 -42
- package/skills/page-authoring/references/interactivity.md +71 -23
- package/skills/page-authoring/references/layout-and-responsive.md +38 -17
- package/skills/page-authoring/references/typography-and-color.md +50 -32
- package/skills/shadcn/SKILL.md +277 -0
- package/skills/shadcn/assets/shadcn-small.png +0 -0
- package/skills/shadcn/assets/shadcn.png +0 -0
- package/skills/shadcn/cli.md +290 -0
- package/skills/shadcn/customization.md +209 -0
- package/skills/shadcn/mcp.md +105 -0
- package/skills/shadcn/registry.md +277 -0
- package/skills/shadcn/rules/base-vs-radix.md +306 -0
- package/skills/shadcn/rules/chat.md +224 -0
- package/skills/shadcn/rules/composition.md +213 -0
- package/skills/shadcn/rules/forms.md +192 -0
- package/skills/shadcn/rules/icons.md +101 -0
- package/skills/shadcn/rules/styling.md +185 -0
- package/src/app/components/asset-view.tsx +11 -11
- package/src/app/components/command/command-menu.tsx +6 -6
- package/src/app/components/command/command.tsx +2 -2
- package/src/app/components/command/home-command-menu.tsx +2 -2
- package/src/app/components/icon-tooltip.tsx +1 -1
- package/src/app/components/language-toggle.tsx +7 -7
- package/src/app/components/sidebar/folder-item.tsx +5 -5
- package/src/app/components/sidebar/icon-picker.tsx +3 -3
- package/src/app/components/sidebar/sidebar-footer.tsx +4 -4
- package/src/app/components/sidebar/sidebar.tsx +6 -6
- package/src/app/components/theme-toggle.tsx +6 -6
- package/src/app/components/themes/theme-detail.tsx +4 -4
- package/src/app/components/themes/themes-gallery.tsx +1 -1
- package/src/app/components/ui/badge.tsx +1 -1
- package/src/app/components/ui/button.tsx +1 -1
- package/src/app/components/ui/card.tsx +1 -1
- package/src/app/components/ui/context-menu.tsx +1 -1
- package/src/app/components/ui/dialog.tsx +2 -2
- package/src/app/components/ui/dropdown-menu.tsx +1 -1
- package/src/app/components/ui/input.tsx +1 -1
- package/src/app/components/ui/label.tsx +1 -1
- package/src/app/components/ui/popover.tsx +1 -1
- package/src/app/components/ui/progress.tsx +1 -1
- package/src/app/components/ui/scroll-area.tsx +1 -1
- package/src/app/components/ui/select.tsx +1 -1
- package/src/app/components/ui/separator.tsx +1 -1
- package/src/app/components/ui/slider.tsx +1 -1
- package/src/app/components/ui/tabs.tsx +1 -1
- package/src/app/components/ui/textarea.tsx +1 -1
- package/src/app/components/ui/toggle-group.tsx +2 -2
- package/src/app/components/ui/toggle.tsx +1 -1
- package/src/app/components/ui/tooltip.tsx +1 -1
- package/src/app/frame/main.tsx +3 -1
- package/src/app/lib/themes.ts +1 -0
- package/src/app/lib/use-restart-server.ts +1 -1
- package/src/app/routes/home-shell.tsx +7 -7
- package/src/app/routes/home.tsx +5 -5
- package/src/app/routes/page.tsx +2 -2
- package/src/app/routes/themes.tsx +1 -1
- package/src/app/virtual.d.ts +3 -0
- 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,
|
|
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.
|
|
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="
|
|
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-
|
|
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-
|
|
3
|
-
import { createViteConfig } from "./config-
|
|
2
|
+
import { findPages, loadUserConfig } from "./open-pages-plugin-C0poXLsx.js";
|
|
3
|
+
import { createViteConfig } from "./config-CDUdFE8N.js";
|
|
4
4
|
import "./update-package-Cxt57pz7.js";
|
|
5
5
|
import { readCoreVersion } from "./version-B6AiiGvT.js";
|
|
6
|
-
import { buildPage } from "./export-
|
|
6
|
+
import { buildPage } from "./export-DbVwSmKW.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-
|
|
60
|
+
const { dev } = await import("../dev-CzJYXFOO.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-
|
|
64
|
+
const { build } = await import("../build-L50pau9v.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-
|
|
68
|
+
const { preview } = await import("../preview-CPufRmuj.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-
|
|
72
|
+
const { exportPages } = await import("../export-BsK6OCpc.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-
|
|
2
|
+
import { PAGE_ID_RE, duplicatePageDir, invalidatePagesModule, loadUserConfig, openPagesPlugin, resolvePageEntry, rmPageDir, updateMetaTitleInSource, validatePageName } from "./open-pages-plugin-C0poXLsx.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
|
|
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
|
|
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
|
-
"
|
|
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-
|
|
3
|
-
import { DEV_SUPERVISED_ENV, RESTART_EXIT_CODE, createViteConfig } from "./config-
|
|
2
|
+
import "./open-pages-plugin-C0poXLsx.js";
|
|
3
|
+
import { DEV_SUPERVISED_ENV, RESTART_EXIT_CODE, createViteConfig } from "./config-CDUdFE8N.js";
|
|
4
4
|
import "./update-package-Cxt57pz7.js";
|
|
5
5
|
import "./version-B6AiiGvT.js";
|
|
6
6
|
import chalk from "chalk";
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { extractMeta, findPages, loadUserConfig, openPagesPlugin } from "./open-pages-plugin-
|
|
1
|
+
import { extractMeta, findPages, loadUserConfig, openPagesPlugin } from "./open-pages-plugin-C0poXLsx.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: {
|
|
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);
|
|
@@ -456,6 +481,9 @@ function rewriteHtmlUrls(html, pageDir) {
|
|
|
456
481
|
const prefix = `/@fs/${normalizePath(pageDir).replace(/^\/+/, "")}/`;
|
|
457
482
|
return html.replace(/(\s(?:src|href)=)(["'])(?!\/|[a-z][a-z0-9+.-]*:|#|\2)(?:\.\/)?([^"']+)\2/gi, (_m, attr, quote, rel) => `${attr}${quote}${prefix}${rel}${quote}`);
|
|
458
483
|
}
|
|
484
|
+
function pagesSignature(entries) {
|
|
485
|
+
return entries.map((e) => `${e.id}:${e.kind}:${e.file}`).join(",");
|
|
486
|
+
}
|
|
459
487
|
function openPagesPlugin(opts) {
|
|
460
488
|
const { userCwd, config, coreVersion } = opts;
|
|
461
489
|
const pagesDir = config.pagesDir ?? "pages";
|
|
@@ -465,6 +493,7 @@ function openPagesPlugin(opts) {
|
|
|
465
493
|
const foldersManifestPath = path.join(pagesRoot, ".folders.json");
|
|
466
494
|
let isDev = false;
|
|
467
495
|
let cssFile = "";
|
|
496
|
+
let generatedSignature = "";
|
|
468
497
|
const pageIdForEntry = (p) => {
|
|
469
498
|
const rel = path.relative(pagesRoot, p);
|
|
470
499
|
if (rel.startsWith("..") || path.isAbsolute(rel)) return null;
|
|
@@ -502,7 +531,11 @@ function openPagesPlugin(opts) {
|
|
|
502
531
|
name: "open-pages",
|
|
503
532
|
config(_c, env) {
|
|
504
533
|
isDev = env.command === "serve";
|
|
505
|
-
cssFile = writePagesCss(userCwd, [
|
|
534
|
+
cssFile = workspaceCssFile(userCwd) ?? writePagesCss(userCwd, [
|
|
535
|
+
pagesRoot,
|
|
536
|
+
themesRoot,
|
|
537
|
+
...WORKSPACE_SOURCE_DIRS.map((d) => path.join(userCwd, d))
|
|
538
|
+
]);
|
|
506
539
|
return { server: { fs: { allow: [userCwd] } } };
|
|
507
540
|
},
|
|
508
541
|
resolveId(id) {
|
|
@@ -515,6 +548,7 @@ function openPagesPlugin(opts) {
|
|
|
515
548
|
async load(id) {
|
|
516
549
|
if (id === resolved(PAGES_VMOD)) {
|
|
517
550
|
const entries = await findPages(userCwd, pagesDir);
|
|
551
|
+
generatedSignature = pagesSignature(entries);
|
|
518
552
|
const { code, ignored } = await generatePagesModule(entries, isDev);
|
|
519
553
|
for (const pageId of ignored) {
|
|
520
554
|
if (warnedInvalidPageIds.has(pageId)) continue;
|
|
@@ -555,13 +589,18 @@ function openPagesPlugin(opts) {
|
|
|
555
589
|
let reloadTimer = null;
|
|
556
590
|
const reload = () => {
|
|
557
591
|
if (reloadTimer) clearTimeout(reloadTimer);
|
|
558
|
-
reloadTimer = setTimeout(() => {
|
|
592
|
+
reloadTimer = setTimeout(async () => {
|
|
559
593
|
reloadTimer = null;
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
594
|
+
try {
|
|
595
|
+
const entries = await findPages(userCwd, pagesDir);
|
|
596
|
+
if (pagesSignature(entries) === generatedSignature) return;
|
|
597
|
+
} catch (err) {
|
|
598
|
+
server.config.logger.error(`[open-pages] failed to rescan pages: ${err}`, { error: err instanceof Error ? err : void 0 });
|
|
599
|
+
}
|
|
600
|
+
invalidatePagesModule(server);
|
|
563
601
|
}, 150);
|
|
564
602
|
};
|
|
603
|
+
const isPageDir = (p) => path.dirname(p) === pagesRoot;
|
|
565
604
|
if (existsSync(pagesRoot)) server.watcher.add(pagesRoot);
|
|
566
605
|
server.watcher.on("add", (p) => {
|
|
567
606
|
if (pageIdForEntry(p)) reload();
|
|
@@ -569,6 +608,12 @@ function openPagesPlugin(opts) {
|
|
|
569
608
|
server.watcher.on("unlink", (p) => {
|
|
570
609
|
if (pageIdForEntry(p)) reload();
|
|
571
610
|
});
|
|
611
|
+
server.watcher.on("addDir", (p) => {
|
|
612
|
+
if (isPageDir(p)) reload();
|
|
613
|
+
});
|
|
614
|
+
server.watcher.on("unlinkDir", (p) => {
|
|
615
|
+
if (isPageDir(p)) reload();
|
|
616
|
+
});
|
|
572
617
|
server.watcher.on("change", (p) => {
|
|
573
618
|
const entry = pageIdForEntry(p);
|
|
574
619
|
if (entry?.kind === "html") queuePageChanged(server, entry.id);
|
|
@@ -629,4 +674,4 @@ async function loadUserConfig(userCwd) {
|
|
|
629
674
|
}
|
|
630
675
|
|
|
631
676
|
//#endregion
|
|
632
|
-
export { PAGE_ID_RE, duplicatePageDir, extractMeta, findPages, loadUserConfig, openPagesPlugin, resolvePageEntry, rmPageDir, updateMetaTitleInSource, validatePageName };
|
|
677
|
+
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-
|
|
3
|
-
import { createViteConfig } from "./config-
|
|
2
|
+
import "./open-pages-plugin-C0poXLsx.js";
|
|
3
|
+
import { createViteConfig } from "./config-CDUdFE8N.js";
|
|
4
4
|
import "./update-package-Cxt57pz7.js";
|
|
5
5
|
import "./version-B6AiiGvT.js";
|
|
6
6
|
import { mergeConfig, preview as preview$1 } from "vite";
|
package/dist/vite/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../design-C13iz9_4.js";
|
|
2
|
-
import "../open-pages-plugin-
|
|
3
|
-
import { createViteConfig } from "../config-
|
|
2
|
+
import "../open-pages-plugin-C0poXLsx.js";
|
|
3
|
+
import { createViteConfig } from "../config-CDUdFE8N.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.
|
|
3
|
+
"version": "0.4.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": {
|
|
@@ -84,8 +84,8 @@
|
|
|
84
84
|
"fast-glob": "^3.3.2",
|
|
85
85
|
"lucide-react": "^1.25.0",
|
|
86
86
|
"next-themes": "^0.4.6",
|
|
87
|
-
"react": "^
|
|
88
|
-
"react-dom": "^
|
|
87
|
+
"react": "^19.2.0",
|
|
88
|
+
"react-dom": "^19.2.0",
|
|
89
89
|
"react-router-dom": "^7.18.1",
|
|
90
90
|
"shadcn": "^4.19.0",
|
|
91
91
|
"sonner": "^2.0.7",
|
|
@@ -93,13 +93,13 @@
|
|
|
93
93
|
"tailwindcss": "^4.2.2",
|
|
94
94
|
"tw-animate-css": "^1.4.0",
|
|
95
95
|
"use-sync-external-store": "^1.6.0",
|
|
96
|
-
"vite": "^
|
|
96
|
+
"vite": "^6.4.3"
|
|
97
97
|
},
|
|
98
98
|
"devDependencies": {
|
|
99
99
|
"@playwright/test": "~1.62.1",
|
|
100
100
|
"@types/node": "^22.19.17",
|
|
101
|
-
"@types/react": "^
|
|
102
|
-
"@types/react-dom": "^
|
|
101
|
+
"@types/react": "^19.2.0",
|
|
102
|
+
"@types/react-dom": "^19.2.0",
|
|
103
103
|
"tsdown": "^0.9.9",
|
|
104
104
|
"typescript": "^5.9.3"
|
|
105
105
|
}
|
|
@@ -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 `<
|
|
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
|
|
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,
|
|
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.
|
|
17
|
-
- If the user picks "no theme", or `themes/` contains no theme markdown files:
|
|
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 —
|
|
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
|
|
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,
|
|
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
|
|