@marver-design/marver 0.2.1 → 0.2.3
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 +1 -0
- package/dist/{build-BYZDMiIS.mjs → build-Bqd6OEsQ.mjs} +2 -2
- package/dist/cli.mjs +3 -3
- package/dist/{dev-Dt9D4O7Z.mjs → dev-9-80L5i5.mjs} +2 -2
- package/dist/init-ClhCgn4v.mjs +358 -0
- package/dist/{manifest-CHmKAAtG.mjs → manifest-mYlO_1Pj.mjs} +78 -2
- package/dist/{plugin-lVQoABEx.mjs → plugin-YVpBNTB3.mjs} +103 -9
- package/package.json +1 -1
- package/src/client/shell/App.tsx +123 -19
- package/src/client/shell/Play.tsx +36 -0
- package/src/client/shell/canvas/Canvas.tsx +54 -7
- package/src/client/shell/canvas/FrameNode.tsx +14 -1
- package/src/client/shell/icons.tsx +2 -0
- package/src/client/shell/store.ts +119 -15
- package/src/client/shell/styles.css +107 -2
- package/src/client/shell/tidy.ts +347 -17
- package/src/client/stage/main.tsx +11 -3
- package/templates/AGENTS-embedded.md +38 -33
- package/templates/AGENTS-studio.md +38 -33
- package/templates/design-tsconfig.json +7 -2
- package/templates/instructions/boards.md +84 -0
- package/templates/instructions/brand.md +60 -0
- package/templates/instructions/components.md +52 -0
- package/templates/instructions/configure.md +44 -0
- package/templates/instructions/craft.md +90 -0
- package/templates/instructions/discover.md +58 -0
- package/templates/instructions/reference/color.md +53 -0
- package/templates/instructions/reference/concepts.md +68 -0
- package/templates/instructions/reference/copy.md +57 -0
- package/templates/instructions/reference/critique.md +53 -0
- package/templates/instructions/reference/delight.md +35 -0
- package/templates/instructions/reference/layout.md +51 -0
- package/templates/instructions/reference/motion.md +66 -0
- package/templates/instructions/reference/operate.md +38 -0
- package/templates/instructions/reference/slop.md +76 -0
- package/templates/instructions/reference/states.md +48 -0
- package/templates/instructions/reference/tune.md +61 -0
- package/templates/instructions/reference/typography.md +45 -0
- package/templates/instructions/review.md +51 -0
- package/templates/instructions/wireframe.md +49 -0
- package/dist/init-CHUZTYG7.mjs +0 -224
package/dist/init-CHUZTYG7.mjs
DELETED
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
import { t as NAME } from "./cli.mjs";
|
|
2
|
-
import { i as DEFAULTS, n as scanFrames, o as detectHost, r as writeManifest } from "./manifest-CHmKAAtG.mjs";
|
|
3
|
-
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
-
import { dirname, join, relative } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
|
-
//#region src/cli/init.ts
|
|
7
|
-
const pkgDir = () => join(dirname(fileURLToPath(import.meta.url)), "..");
|
|
8
|
-
/** Idempotent scaffolder: never overwrites existing files; every host-repo touch prints a diff. */
|
|
9
|
-
function init(root, opts) {
|
|
10
|
-
const host = detectHost(root);
|
|
11
|
-
const design = join(root, "design");
|
|
12
|
-
const templates = join(pkgDir(), "templates");
|
|
13
|
-
const created = [];
|
|
14
|
-
const write = (rel, content) => {
|
|
15
|
-
const file = join(design, rel);
|
|
16
|
-
if (existsSync(file)) return;
|
|
17
|
-
mkdirSync(dirname(file), { recursive: true });
|
|
18
|
-
writeFileSync(file, content);
|
|
19
|
-
created.push(`design/${rel}`);
|
|
20
|
-
};
|
|
21
|
-
write("config.ts", configTemplate(opts.mode));
|
|
22
|
-
if (host.themeCss) {
|
|
23
|
-
const relCss = relative(design, join(root, host.themeCss)).split("\\").join("/");
|
|
24
|
-
write("theme.css", themeWrapper(relCss, host.tailwind === 4));
|
|
25
|
-
} else console.warn(`[${NAME}] no theme CSS detected - create design/theme.css importing your app's stylesheet when you have one (or set \`theme\` in design/config.ts).`);
|
|
26
|
-
write("providers.tsx", providersTemplate(host.router, host.toaster, host.routerPkg));
|
|
27
|
-
const agents = AGENTS_MARKER + "\n" + readFileSync(join(templates, `AGENTS-${opts.mode}.md`), "utf8").replaceAll("{{UI_GUIDANCE}}", uiGuidance(host)).replace(/\{\{NEXT_NOTES\}\}\n?/, host.router === "next" ? NEXT_NOTES : "");
|
|
28
|
-
const agentsPath = join(design, "AGENTS.md");
|
|
29
|
-
if (!existsSync(agentsPath)) write("AGENTS.md", agents);
|
|
30
|
-
else {
|
|
31
|
-
const current = readFileSync(agentsPath, "utf8");
|
|
32
|
-
if (current.startsWith(AGENTS_MARKER) && current !== agents) {
|
|
33
|
-
writeFileSync(agentsPath, agents);
|
|
34
|
-
created.push("design/AGENTS.md (regenerated - detected stack changed)");
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
write("tsconfig.json", existsSync(join(root, "tsconfig.json")) ? readFileSync(join(templates, "design-tsconfig.json"), "utf8") : STANDALONE_TSCONFIG);
|
|
38
|
-
write(".gitignore", ".local/\n.dist/\n");
|
|
39
|
-
write("scenes/_layout.tsx", readFileSync(join(templates, "root-layout.tsx"), "utf8"));
|
|
40
|
-
if (!existsSync(join(design, "boards"))) {
|
|
41
|
-
mkdirSync(join(design, "boards"), { recursive: true });
|
|
42
|
-
writeFileSync(join(design, "boards", ".gitkeep"), "");
|
|
43
|
-
created.push("design/boards/");
|
|
44
|
-
}
|
|
45
|
-
if (opts.demo && !existsSync(join(design, "scenes", "demo"))) {
|
|
46
|
-
cpSync(join(templates, "demo"), join(design, "scenes", "demo"), { recursive: true });
|
|
47
|
-
created.push("design/scenes/demo/ (3 frames)");
|
|
48
|
-
}
|
|
49
|
-
if (host.tsconfigSweepsDesign) patchTsconfigExclude(root);
|
|
50
|
-
writeManifest(root, scanFrames(root));
|
|
51
|
-
console.log(`\n${NAME} initialized (${opts.mode} mode). Created:`);
|
|
52
|
-
for (const f of created) console.log(` + ${f}`);
|
|
53
|
-
if (host.router === "next") console.log(`\n note: Next.js support is partial - frames render outside Next, so next/font, next/image and Server Components do not exist inside them (details in design/AGENTS.md).`);
|
|
54
|
-
if (noApp(host)) console.warn(`
|
|
55
|
-
┌─ NO APP DETECTED ─────────────────────────────────────────────────────┐
|
|
56
|
-
│ This repo has no framework, no theme CSS, and no component library. │
|
|
57
|
-
│ ${NAME} builds frames from YOUR components - with none, frames become │
|
|
58
|
-
│ hand-rolled CSS that cannot be promoted into an app later. │
|
|
59
|
-
│ │
|
|
60
|
-
│ Set up the app first, then re-run init (it is idempotent and will │
|
|
61
|
-
│ fill in what it detects). For a web app or site: │
|
|
62
|
-
│ npx create-next-app@latest . --ts --tailwind --app --src-dir │
|
|
63
|
-
│ npx shadcn@latest init │
|
|
64
|
-
│ │
|
|
65
|
-
│ design/AGENTS.md was generated with a STOP instruction so your agent │
|
|
66
|
-
│ does not design against a component library that does not exist. │
|
|
67
|
-
└───────────────────────────────────────────────────────────────────────┘`);
|
|
68
|
-
console.log(`\n commit design/ - only .local/ is ignored`);
|
|
69
|
-
console.log(` uninstall = delete design/, remove the ${NAME} dependency${host.tsconfigSweepsDesign ? ", revert the \"design\" line in tsconfig exclude" : ""}`);
|
|
70
|
-
console.log(`\n next: npx ${NAME} dev (canvas on http://localhost:${DEFAULTS.port} by default)\n`);
|
|
71
|
-
if (!noApp(host)) console.log(` then, to your agent: "Read design/AGENTS.md. Build an onboarding scene - welcome, form, done - mobile-first, using our components."\n`);
|
|
72
|
-
}
|
|
73
|
-
const AGENTS_MARKER = "<!-- generated by marver init from the detected stack; re-running init regenerates this file when detection changes. Made edits you want to keep? Delete this line and init will never touch the file again. -->";
|
|
74
|
-
/** No framework, no theme, no component alias = nothing to build frames FROM. */
|
|
75
|
-
const noApp = (host) => !host.router && !host.tailwind && !host.shadcn && !host.themeCss;
|
|
76
|
-
/** The UI line of AGENTS.md, matched to what detection actually found (friction log #1). */
|
|
77
|
-
function uiGuidance(host) {
|
|
78
|
-
if (host.shadcn) return `Use the app's UI: import from ${host.shadcn.uiAlias}; style with the app's Tailwind classes.`;
|
|
79
|
-
if (host.tailwind) return `Style with the app's Tailwind classes and design tokens; there is no detected component library - extract shared pieces into design/components/.`;
|
|
80
|
-
return `STOP - this repo has no component library, no Tailwind, and no theme. Frames built from hand-rolled CSS cannot be promoted into an app later. Ask the human to set up the app first (framework + styling), then re-run \`npx ${NAME} init\` so this contract regenerates against the real stack.`;
|
|
81
|
-
}
|
|
82
|
-
/** Next.js frames render OUTSIDE Next - say concretely what that means (friction log #10/#11). */
|
|
83
|
-
const NEXT_NOTES = `- Next.js caveats (frames render in Vite, outside Next):
|
|
84
|
-
next/font does not exist here - CSS variables it injects (e.g. --font-geist-sans) are
|
|
85
|
-
undefined in frames, so give every font token a real fallback chain in the app's CSS:
|
|
86
|
-
--font-sans: var(--font-geist-sans, ui-sans-serif, system-ui, sans-serif).
|
|
87
|
-
next/image and next/link render as plain img/a via shims at best - prefer <img> and
|
|
88
|
-
data-goto in frames. Server Components and server actions cannot run: frames are
|
|
89
|
-
client components importing client components.
|
|
90
|
-
`;
|
|
91
|
-
const STANDALONE_TSCONFIG = `{
|
|
92
|
-
"compilerOptions": {
|
|
93
|
-
"target": "ES2022",
|
|
94
|
-
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
95
|
-
"module": "ESNext",
|
|
96
|
-
"moduleResolution": "bundler",
|
|
97
|
-
"jsx": "react-jsx",
|
|
98
|
-
"strict": true,
|
|
99
|
-
"skipLibCheck": true,
|
|
100
|
-
"noEmit": true
|
|
101
|
-
},
|
|
102
|
-
"include": ["."]
|
|
103
|
-
}
|
|
104
|
-
`;
|
|
105
|
-
function patchTsconfigExclude(root) {
|
|
106
|
-
const file = join(root, "tsconfig.json");
|
|
107
|
-
if (!existsSync(file)) return;
|
|
108
|
-
const raw = readFileSync(file, "utf8");
|
|
109
|
-
try {
|
|
110
|
-
let next;
|
|
111
|
-
if (/"exclude"\s*:/.test(raw)) next = raw.replace(/("exclude"\s*:\s*\[)/, "$1\"design\", ");
|
|
112
|
-
else {
|
|
113
|
-
const at = firstJsonBrace(raw);
|
|
114
|
-
if (at < 0) throw new Error("no object brace found");
|
|
115
|
-
next = raw.slice(0, at + 1) + "\n \"exclude\": [\"design\"]," + raw.slice(at + 1);
|
|
116
|
-
}
|
|
117
|
-
if (next === raw) throw new Error("no anchor matched");
|
|
118
|
-
writeFileSync(file, next);
|
|
119
|
-
console.log(`\n patched tsconfig.json (the only host file touched):`);
|
|
120
|
-
console.log(` + "design" added to "exclude" (revert this line to fully uninstall)`);
|
|
121
|
-
} catch {
|
|
122
|
-
console.warn(` could not patch tsconfig.json - add "design" to its "exclude" yourself.`);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
/** Index of the first `{` outside //, /* *\/ comments and strings (tsconfig is JSONC). */
|
|
126
|
-
function firstJsonBrace(src) {
|
|
127
|
-
let inLine = false, inBlock = false, inStr = false;
|
|
128
|
-
for (let i = 0; i < src.length; i++) {
|
|
129
|
-
const c = src[i], n = src[i + 1];
|
|
130
|
-
if (inLine) {
|
|
131
|
-
if (c === "\n") inLine = false;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (inBlock) {
|
|
135
|
-
if (c === "*" && n === "/") {
|
|
136
|
-
inBlock = false;
|
|
137
|
-
i++;
|
|
138
|
-
}
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
if (inStr) {
|
|
142
|
-
if (c === "\\") i++;
|
|
143
|
-
else if (c === "\"") inStr = false;
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (c === "/" && n === "/") {
|
|
147
|
-
inLine = true;
|
|
148
|
-
i++;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (c === "/" && n === "*") {
|
|
152
|
-
inBlock = true;
|
|
153
|
-
i++;
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
if (c === "\"") {
|
|
157
|
-
inStr = true;
|
|
158
|
-
continue;
|
|
159
|
-
}
|
|
160
|
-
if (c === "{") return i;
|
|
161
|
-
}
|
|
162
|
-
return -1;
|
|
163
|
-
}
|
|
164
|
-
const configTemplate = (mode) => `// ${NAME} config - OPTIONAL. Delete this file and everything still works on defaults.
|
|
165
|
-
// Theme lives in design/theme.css (it imports your app's real stylesheet) - not here.
|
|
166
|
-
// Sharp edges (native Node TS import): erasable syntax only (no enums/namespaces),
|
|
167
|
-
// relative imports need extensions, tsconfig paths are ignored here.
|
|
168
|
-
export default {
|
|
169
|
-
mode: ${JSON.stringify(mode)},
|
|
170
|
-
// Device widths for frames and the Devices view. Rename, retune, or uncomment tv.
|
|
171
|
-
viewports: {
|
|
172
|
-
mobile: { width: 390, height: 844 },
|
|
173
|
-
tablet: { width: 768, height: 1024 },
|
|
174
|
-
laptop: { width: 1280, height: 800 },
|
|
175
|
-
monitor: { width: 1920, height: 1080 },
|
|
176
|
-
// tv: { width: 3840, height: 2160 },
|
|
177
|
-
},
|
|
178
|
-
themes: ["light", "dark"],
|
|
179
|
-
port: ${DEFAULTS.port},
|
|
180
|
-
// Canvas zoom feel: 1 = default, 1.2 = 20% faster, 0.8 = 20% slower.
|
|
181
|
-
// zoomSpeed: 1,
|
|
182
|
-
// Publishing (\`${NAME} build\` + \`${NAME} serve\`): gate identity + branding footer.
|
|
183
|
-
// name/logo default to the host package.json name and design/logo.svg (then public/).
|
|
184
|
-
// branding is the small "Powered by Marver.design" line under the gate. Marver is
|
|
185
|
-
// free, and that line is how it spreads - we'd love it if you leave it on, but it
|
|
186
|
-
// is yours to remove, no strings: share: { branding: false }.
|
|
187
|
-
// share: { name: "My App", logo: "design/logo.svg", branding: true },
|
|
188
|
-
}
|
|
189
|
-
`;
|
|
190
|
-
const themeWrapper = (relCss, v4) => `/* ${NAME} theme wrapper - imports the app's real theme; the app's own build never sees design/. */
|
|
191
|
-
@import "${relCss}";
|
|
192
|
-
${v4 ? `@source "./";\n` : ""}`;
|
|
193
|
-
function providersTemplate(router, toaster, routerPkg = "react-router-dom") {
|
|
194
|
-
const imports = [`import type { ReactNode } from 'react'`];
|
|
195
|
-
let open = "", close = "";
|
|
196
|
-
if (router === "react-router") {
|
|
197
|
-
imports.push(`import { MemoryRouter } from '${routerPkg}'`);
|
|
198
|
-
open += "<MemoryRouter>";
|
|
199
|
-
close = "</MemoryRouter>" + close;
|
|
200
|
-
}
|
|
201
|
-
let toasterEl = "";
|
|
202
|
-
if (toaster === "sonner") {
|
|
203
|
-
imports.push(`import { Toaster } from 'sonner'`);
|
|
204
|
-
toasterEl = "<Toaster />";
|
|
205
|
-
}
|
|
206
|
-
if (toaster === "react-hot-toast") {
|
|
207
|
-
imports.push(`import { Toaster } from 'react-hot-toast'`);
|
|
208
|
-
toasterEl = "<Toaster />";
|
|
209
|
-
}
|
|
210
|
-
return `// Mock contexts wrapped around every frame. Scaffolded by ${NAME} init from what it detected - yours to edit.
|
|
211
|
-
${imports.join("\n")}
|
|
212
|
-
|
|
213
|
-
export default function Providers({ children }: { children: ReactNode }) {
|
|
214
|
-
return (
|
|
215
|
-
${open || "<>"}
|
|
216
|
-
{children}
|
|
217
|
-
${toasterEl}
|
|
218
|
-
${close || "</>"}
|
|
219
|
-
)
|
|
220
|
-
}
|
|
221
|
-
`;
|
|
222
|
-
}
|
|
223
|
-
//#endregion
|
|
224
|
-
export { init };
|