uniweb 0.1.7 → 0.2.1

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 (3) hide show
  1. package/README.md +291 -59
  2. package/package.json +3 -3
  3. package/src/index.js +282 -150
package/README.md CHANGED
@@ -1,24 +1,116 @@
1
1
  # uniweb
2
2
 
3
- CLI for the Uniweb Component Web Platform.
3
+ Create a well-structured Vite + React site with content/code separation, file-based routing, localization, and structured content—out of the box. The architecture scales to dynamic content management and collaborative visual editing when you need it.
4
4
 
5
5
  ## Quick Start
6
6
 
7
- Create a new Uniweb project with a starter template:
8
-
9
- ```bash
10
- npx uniweb@latest create my-project --template workspace
11
- ```
12
-
13
- This scaffolds a Vite project with everything wired—routing, state, and build pipeline ready. You get a site and a Foundation in a monorepo, pre-configured to work together.
7
+ Create a new Uniweb project:
14
8
 
15
9
  ```bash
10
+ npx uniweb@latest create my-project
16
11
  cd my-project
17
12
  pnpm install
18
13
  pnpm dev
19
14
  ```
20
15
 
21
- No heavy framework to learn. Foundations are React component libraries built with Vite and styled with Tailwind. Sites are Vite apps that load content from markdown files. The CLI handles the wiring.
16
+ You get a workspace with two packages:
17
+
18
+ - **`site/`** — Content, pages, entry point
19
+ - **`foundation/`** — Your React components
20
+
21
+ Content authors work in markdown. Component authors work in React. Neither can break the other's work, and component updates flow to every site using them.
22
+
23
+ ## What You Get
24
+
25
+ ```
26
+ my-project/
27
+ ├── site/ # Content + configuration
28
+ │ ├── pages/ # File-based routing
29
+ │ │ └── home/
30
+ │ │ ├── page.yml # Page metadata
31
+ │ │ └── 1-hero.md # Section content
32
+ │ ├── locales/ # i18n (mirrors pages/)
33
+ │ │ └── es/
34
+ │ └── public/ # Static assets
35
+
36
+ └── foundation/ # Your components
37
+ └── src/
38
+ └── components/
39
+ └── Hero/
40
+ └── index.jsx
41
+ ```
42
+
43
+ **Pages are folders.** Create `pages/about/` with a markdown file inside → visit `/about`. That's the whole routing model.
44
+
45
+ ### Content as Markdown
46
+
47
+ ```markdown
48
+ ---
49
+ component: Hero
50
+ theme: dark
51
+ ---
52
+
53
+ # Welcome
54
+
55
+ Build something great.
56
+
57
+ [Get Started](#)
58
+ ```
59
+
60
+ Frontmatter specifies the component and configuration. The body contains the actual content—headings, paragraphs, links, images—which gets semantically parsed into structured data your component receives.
61
+
62
+ ### Beyond Markdown
63
+
64
+ For content that doesn't fit markdown patterns—products, team members, events—use JSON blocks with schema tags:
65
+
66
+ ````markdown
67
+ ```json #team-member
68
+ {
69
+ "name": "Sarah Chen",
70
+ "role": "Lead Architect"
71
+ }
72
+ ```
73
+ ````
74
+
75
+ Components receive validated, localized data. Natural content stays in markdown; structured data goes in JSON blocks.
76
+
77
+ ### Components as React
78
+
79
+ ```jsx
80
+ export function Hero({ content, params }) {
81
+ const { title } = content.main.header;
82
+ const { paragraphs, links } = content.main.body;
83
+ const { theme = 'light' } = params;
84
+
85
+ return (
86
+ <section className={`py-20 text-center ${theme === 'dark' ? 'bg-gray-900 text-white' : ''}`}>
87
+ <h1 className="text-4xl font-bold">{title}</h1>
88
+ <p className="text-xl text-gray-600">{paragraphs[0]}</p>
89
+ {links[0] && (
90
+ <a href={links[0].url} className="mt-8 px-6 py-3 bg-blue-600 text-white rounded inline-block">
91
+ {links[0].text}
92
+ </a>
93
+ )}
94
+ </section>
95
+ );
96
+ }
97
+ ```
98
+
99
+ Standard React. Standard Tailwind. The `{ content, params }` interface is only for *exposed* components—the ones content creators select in markdown frontmatter. Internal components (the majority of your codebase) use regular React props.
100
+
101
+ No framework to learn. Foundations are purpose-built component systems designed for a specific domain (marketing, documentation, learning, etc.). Sites are Vite apps that load content from markdown files. The CLI handles the wiring.
102
+
103
+ ## The Bigger Picture
104
+
105
+ The structure you start with scales without rewrites:
106
+
107
+ 1. **Single project** — One site, one component library. Most projects stay here.
108
+ 2. **Multi-site** — One foundation powers multiple sites. Release it once, updates propagate automatically.
109
+ 3. **Full platform** — [uniweb.app](https://uniweb.app) adds visual editing, live content management, and team collaboration. Your foundation plugs in and its components become native to the editor.
110
+
111
+ Start with local markdown files deployed anywhere. Connect to [uniweb.app](https://uniweb.app) when you're ready for dynamic content and team collaboration.
112
+
113
+ ---
22
114
 
23
115
  ## Installation
24
116
 
@@ -42,9 +134,9 @@ uniweb create [project-name] [options]
42
134
 
43
135
  **Options:**
44
136
 
45
- | Option | Description |
46
- | ------------------- | ------------------------------------------------------ |
47
- | `--template <type>` | Project template: `workspace`, `site`, or `foundation` |
137
+ | Option | Description |
138
+ | ------------------- | ------------------------------------- |
139
+ | `--template <type>` | Project template: `single` or `multi` |
48
140
 
49
141
  **Examples:**
50
142
 
@@ -52,17 +144,14 @@ uniweb create [project-name] [options]
52
144
  # Interactive prompts
53
145
  uniweb create
54
146
 
55
- # Create with specific name
147
+ # Create with specific name (defaults to single template)
56
148
  uniweb create my-project
57
149
 
58
- # Full workspace (site + foundation)
59
- uniweb create my-workspace --template workspace
60
-
61
- # Standalone foundation
62
- uniweb create my-foundation --template foundation
150
+ # Single project with site + foundation
151
+ uniweb create my-project --template single
63
152
 
64
- # Standalone site
65
- uniweb create my-site --template site
153
+ # Multi-site/foundation monorepo
154
+ uniweb create my-workspace --template multi
66
155
  ```
67
156
 
68
157
  ### `build`
@@ -75,9 +164,10 @@ uniweb build [options]
75
164
 
76
165
  **Options:**
77
166
 
78
- | Option | Description |
79
- | ----------------- | --------------------------------------------------------------------- |
80
- | `--target <type>` | Build target: `foundation` or `site` (auto-detected if not specified) |
167
+ | Option | Description |
168
+ | ------------------- | --------------------------------------------------------------------- |
169
+ | `--target <type>` | Build target: `foundation` or `site` (auto-detected if not specified) |
170
+ | `--platform <name>` | Deployment platform (e.g., `vercel`) for platform-specific output |
81
171
 
82
172
  **Examples:**
83
173
 
@@ -90,56 +180,112 @@ uniweb build --target foundation
90
180
 
91
181
  # Explicitly build as site
92
182
  uniweb build --target site
183
+
184
+ # Build for Vercel deployment
185
+ uniweb build --platform vercel
93
186
  ```
94
187
 
95
188
  ## Project Templates
96
189
 
97
- ### Workspace
190
+ ### Single (Default)
98
191
 
99
- A monorepo with both a site and foundation for co-development.
192
+ A minimal workspace with a site and foundation as sibling packages. This is the recommended starting point.
100
193
 
101
194
  ```
102
- my-workspace/
103
- ├── package.json
104
- ├── pnpm-workspace.yaml
105
- └── packages/
106
- ├── site/ # Website using the foundation
107
- └── foundation/ # Component library
195
+ my-project/
196
+ ├── package.json # Workspace root (includes workspaces field for npm)
197
+ ├── pnpm-workspace.yaml # Pre-configured for evolution (see below)
198
+
199
+ ├── site/ # Site package (content + entry)
200
+ │ ├── package.json
201
+ │ ├── vite.config.js
202
+ │ ├── index.html
203
+ │ ├── site.yml
204
+ │ ├── src/
205
+ │ │ └── main.jsx # Thin entry point
206
+ │ ├── pages/ # Content structure
207
+ │ │ └── home/
208
+ │ │ ├── page.yml
209
+ │ │ └── 1-hero.md
210
+ │ ├── locales/ # i18n (mirrors pages/)
211
+ │ │ └── es/
212
+ │ │ └── home/
213
+ │ └── public/ # Static assets
214
+
215
+ └── foundation/ # Foundation package (components)
216
+ ├── package.json
217
+ ├── vite.config.js
218
+ ├── src/
219
+ │ ├── index.js # Component registry
220
+ │ ├── styles.css # Tailwind
221
+ │ ├── meta.js # Foundation metadata
222
+ │ └── components/
223
+ │ └── Hero/
224
+ │ ├── index.jsx
225
+ │ └── meta.js
226
+ └── ...
108
227
  ```
109
228
 
110
- ### Site
229
+ **Key characteristics:**
111
230
 
112
- A standalone site using an existing foundation.
231
+ - **Convention-compliant** Each package has its own `src/`
232
+ - **Clear dep boundaries** — Component libs → `foundation/package.json`, runtime → `site/package.json`
233
+ - **Zero extraction** — `foundation/` is already a complete, publishable package
234
+ - **Scales naturally** — Rename to `sites/marketing/` and `foundations/marketing/` when needed
235
+
236
+ ### Multi
237
+
238
+ A monorepo for multi-site or multi-foundation development.
113
239
 
114
240
  ```
115
- my-site/
116
- ├── package.json
117
- ├── vite.config.js
118
- ├── site.yml
119
- ├── src/
120
- └── main.jsx
121
- └── pages/
122
- └── home/
123
- ├── page.yml
124
- └── 1-hero.md
241
+ my-workspace/
242
+ ├── package.json # Workspace root (includes workspaces field for npm)
243
+ ├── pnpm-workspace.yaml # Same config as site template
244
+
245
+ ├── sites/
246
+ ├── marketing/ # Main marketing site
247
+ │ │ ├── package.json
248
+ │ │ ├── site.yml
249
+ │ │ ├── src/
250
+ │ │ ├── pages/
251
+ │ │ └── ...
252
+ │ └── docs/ # Documentation site
253
+
254
+ └── foundations/
255
+ ├── marketing/ # Marketing foundation
256
+ │ ├── package.json
257
+ │ ├── src/components/
258
+ │ └── ...
259
+ └── documentation/ # Documentation foundation
125
260
  ```
126
261
 
127
- ### Foundation
262
+ Use this when you need:
128
263
 
129
- A standalone component library.
264
+ - Multiple sites sharing foundations
265
+ - Multiple foundations for different purposes
266
+ - A testing site for foundation development
130
267
 
131
- ```
132
- my-foundation/
133
- ├── package.json
134
- ├── vite.config.js
135
- ├── tailwind.config.js
136
- └── src/
137
- ├── meta.js # Foundation metadata
138
- ├── styles.css
139
- └── components/
140
- └── Hero/
141
- ├── index.jsx
142
- └── meta.js
268
+ ## Dependency Management
269
+
270
+ Each package manages its own dependencies:
271
+
272
+ **`site/package.json`:**
273
+
274
+ - `@uniweb/runtime`
275
+ - `@my-project/foundation` (workspace link)
276
+ - Vite, Tailwind (dev)
277
+
278
+ **`foundation/package.json`:**
279
+
280
+ - Component libraries (carousel, icons, etc.)
281
+ - React as peer dependency
282
+
283
+ ```bash
284
+ # Add component dependency
285
+ cd foundation && pnpm add embla-carousel
286
+
287
+ # Site references foundation via workspace
288
+ # No path gymnastics needed
143
289
  ```
144
290
 
145
291
  ## Foundation Build Process
@@ -165,10 +311,96 @@ dist/
165
311
  └── [preset].webp
166
312
  ```
167
313
 
314
+ ## Workspace Configuration
315
+
316
+ Both templates use the same unified workspace configuration:
317
+
318
+ ```yaml
319
+ # pnpm-workspace.yaml
320
+ packages:
321
+ - "site"
322
+ - "foundation"
323
+ - "sites/*"
324
+ - "foundations/*"
325
+ ```
326
+
327
+ ```json
328
+ // package.json (workspaces field for npm compatibility)
329
+ {
330
+ "workspaces": ["site", "foundation", "sites/*", "foundations/*"]
331
+ }
332
+ ```
333
+
334
+ This means **no config changes when evolving your project**.
335
+
336
+ ## Evolving Your Project
337
+
338
+ The workspace is pre-configured for growth. Choose your path:
339
+
340
+ **Add alongside existing structure:**
341
+
342
+ ```bash
343
+ # Keep site/ and foundation/, add more in sites/ and foundations/
344
+ mkdir -p sites/docs
345
+ mkdir -p foundations/documentation
346
+ ```
347
+
348
+ **Or migrate to multi-site structure:**
349
+
350
+ ```bash
351
+ # Move and rename by purpose
352
+ mv site sites/marketing
353
+ mv foundation foundations/marketing
354
+
355
+ # Update package names and workspace dependencies in package.json files
356
+ ```
357
+
358
+ Both patterns work simultaneously—evolve gradually as needed.
359
+
360
+ ## Releasing a Foundation
361
+
362
+ Publish your foundation to [uniweb.app](https://uniweb.app) to make it available for your sites:
363
+
364
+ ```bash
365
+ uniweb login # First time only
366
+ uniweb build
367
+ uniweb publish # Publishes the foundation in the current directory
368
+ ```
369
+
370
+ Each release creates a new version. Sites link to foundations at runtime and control their own update strategy—automatic, minor-only, patch-only, or pinned to a specific version. You own your foundations and license them to sites—yours or your clients'.
371
+
372
+ You can also publish to npm:
373
+
374
+ ```bash
375
+ npm publish
376
+ ```
377
+
378
+ ## FAQ
379
+
380
+ **How is this different from MDX?**
381
+
382
+ MDX blends markdown and JSX—content authors write code. Uniweb keeps them separate: content stays in markdown, components stay in React. Content authors can't break components, and component updates don't require content changes.
383
+
384
+ **How is this different from Astro?**
385
+
386
+ Astro is a static site generator. Uniweb is a content architecture that works with any deployment (static, SSR, or platform-managed). The Foundation model means components are reusable across sites and ready for visual editing.
387
+
388
+ **Do I need uniweb.app?**
389
+
390
+ No. Local markdown files work great for developer-managed sites. The platform adds dynamic content, visual editing, and team collaboration when you need it.
391
+
392
+ **Is this SEO-friendly?**
393
+
394
+ Yes. Content is pre-embedded in the initial HTML—no fetch waterfalls, no layout shifts, instant interaction. Meta tags are generated per page for proper social sharing previews. SSG and SSR are also supported.
395
+
396
+ **What about dynamic routes?**
397
+
398
+ Pages can define data sources that auto-generate subroutes. A `/blog` page can have an index (listing) and a `[slug]` template that renders each post. No manual folders for every entry.
399
+
168
400
  ## Related Packages
169
401
 
170
- - [`@uniweb/build`](https://github.com/uniweb/build) - Foundation build tooling
171
- - [`@uniweb/runtime`](https://github.com/uniweb/runtime) - Runtime loader for sites
402
+ - [`@uniweb/build`](https://github.com/uniweb/build) Foundation build tooling
403
+ - [`@uniweb/runtime`](https://github.com/uniweb/runtime) Runtime loader for sites
172
404
 
173
405
  ## License
174
406
 
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.1.7",
4
- "description": "CLI for the Uniweb Component Web Platform",
3
+ "version": "0.2.1",
4
+ "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
7
- "uniweb": "./src/index.js"
7
+ "uniweb": "src/index.js"
8
8
  },
9
9
  "files": [
10
10
  "src"
package/src/index.js CHANGED
@@ -7,9 +7,8 @@
7
7
  *
8
8
  * Usage:
9
9
  * npx uniweb create [project-name]
10
- * npx uniweb create --template site
11
- * npx uniweb create --template foundation
12
- * npx uniweb create --template workspace
10
+ * npx uniweb create --template single # site/ + foundation/ (default)
11
+ * npx uniweb create --template multi # sites/* + foundations/*
13
12
  * npx uniweb build
14
13
  * npx uniweb build --target foundation
15
14
  */
@@ -51,17 +50,13 @@ function title(message) {
51
50
 
52
51
  // Template definitions
53
52
  const templates = {
54
- workspace: {
55
- name: 'Site + Foundation Workspace',
56
- description: 'Monorepo with a site and foundation for co-development',
53
+ single: {
54
+ name: 'Single Project',
55
+ description: 'One site + one foundation in site/ and foundation/ (recommended)',
57
56
  },
58
- site: {
59
- name: 'Site Only',
60
- description: 'A site that uses an existing foundation',
61
- },
62
- foundation: {
63
- name: 'Foundation Only',
64
- description: 'A standalone foundation package',
57
+ multi: {
58
+ name: 'Multi-Site Project',
59
+ description: 'Multiple sites and foundations in sites/* and foundations/*',
65
60
  },
66
61
  }
67
62
 
@@ -92,7 +87,7 @@ async function main() {
92
87
 
93
88
  // Parse arguments
94
89
  let projectName = args[1]
95
- let templateType = null
90
+ let templateType = "single"; // or null for iteractive selection
96
91
 
97
92
  // Check for --template flag
98
93
  const templateIndex = args.indexOf('--template')
@@ -126,19 +121,14 @@ async function main() {
126
121
  message: 'What would you like to create?',
127
122
  choices: [
128
123
  {
129
- title: templates.workspace.name,
130
- description: templates.workspace.description,
131
- value: 'workspace',
132
- },
133
- {
134
- title: templates.site.name,
135
- description: templates.site.description,
136
- value: 'site',
124
+ title: templates.single.name,
125
+ description: templates.single.description,
126
+ value: 'single',
137
127
  },
138
128
  {
139
- title: templates.foundation.name,
140
- description: templates.foundation.description,
141
- value: 'foundation',
129
+ title: templates.multi.name,
130
+ description: templates.multi.description,
131
+ value: 'multi',
142
132
  },
143
133
  ],
144
134
  },
@@ -169,14 +159,11 @@ async function main() {
169
159
 
170
160
  // Generate project based on template
171
161
  switch (templateType) {
172
- case 'workspace':
173
- await createWorkspace(projectDir, projectName)
174
- break
175
- case 'site':
176
- await createSite(projectDir, projectName)
162
+ case 'single':
163
+ await createSingleProject(projectDir, projectName)
177
164
  break
178
- case 'foundation':
179
- await createFoundation(projectDir, projectName)
165
+ case 'multi':
166
+ await createMultiProject(projectDir, projectName)
180
167
  break
181
168
  }
182
169
 
@@ -186,15 +173,7 @@ async function main() {
186
173
  log(`Next steps:\n`)
187
174
  log(` ${colors.cyan}cd ${projectName}${colors.reset}`)
188
175
  log(` ${colors.cyan}pnpm install${colors.reset}`)
189
-
190
- if (templateType === 'workspace') {
191
- log(` ${colors.cyan}pnpm dev${colors.reset}`)
192
- } else if (templateType === 'site') {
193
- log(` ${colors.cyan}pnpm dev${colors.reset}`)
194
- } else {
195
- log(` ${colors.cyan}pnpm build${colors.reset}`)
196
- }
197
-
176
+ log(` ${colors.cyan}pnpm dev${colors.reset}`)
198
177
  log('')
199
178
  }
200
179
 
@@ -210,49 +189,62 @@ ${colors.bright}Commands:${colors.reset}
210
189
  build Build the current project
211
190
 
212
191
  ${colors.bright}Create Options:${colors.reset}
213
- --template <type> Project template (workspace, site, foundation)
192
+ --template <type> Project template (single, multi)
214
193
 
215
194
  ${colors.bright}Build Options:${colors.reset}
216
195
  --target <type> Build target (foundation, site) - auto-detected if not specified
196
+ --platform <name> Deployment platform (e.g., vercel) for platform-specific output
217
197
 
218
198
  ${colors.bright}Examples:${colors.reset}
219
199
  npx uniweb create my-project
220
- npx uniweb create my-site --template site
221
- npx uniweb create my-foundation --template foundation
200
+ npx uniweb create my-project --template single
201
+ npx uniweb create my-workspace --template multi
222
202
  npx uniweb build
223
203
  npx uniweb build --target foundation
224
204
 
225
205
  ${colors.bright}Templates:${colors.reset}
226
- workspace Site + Foundation monorepo for co-development
227
- site Standalone site using an existing foundation
228
- foundation Standalone foundation package
206
+ single One site + one foundation in site/ and foundation/ (default)
207
+ multi Multiple sites and foundations in sites/* and foundations/*
229
208
  `)
230
209
  }
231
210
 
232
211
  // Template generators
233
- async function createWorkspace(projectDir, projectName) {
212
+
213
+ /**
214
+ * Creates the common project base: package.json, pnpm-workspace.yaml, .gitignore
215
+ * Both single and multi templates share the same workspace configuration.
216
+ */
217
+ function createProjectBase(projectDir, projectName, defaultFilter) {
234
218
  mkdirSync(projectDir, { recursive: true })
235
219
 
236
- // Root package.json
220
+ // Root package.json (workspaces field for npm compatibility)
237
221
  writeJSON(join(projectDir, 'package.json'), {
238
222
  name: projectName,
239
223
  version: '0.1.0',
240
224
  private: true,
241
225
  type: 'module',
242
- workspaces: ['packages/*'],
243
226
  scripts: {
244
- dev: 'pnpm --filter site dev',
245
- 'dev:runtime': 'VITE_FOUNDATION_MODE=runtime pnpm --filter site dev',
227
+ dev: `pnpm --filter ${defaultFilter} dev`,
228
+ 'dev:runtime': `VITE_FOUNDATION_MODE=runtime pnpm --filter ${defaultFilter} dev`,
246
229
  build: 'pnpm -r build',
247
230
  },
231
+ workspaces: [
232
+ 'site',
233
+ 'foundation',
234
+ 'sites/*',
235
+ 'foundations/*',
236
+ ],
248
237
  pnpm: {
249
238
  onlyBuiltDependencies: ['esbuild', 'sharp'],
250
239
  },
251
240
  })
252
241
 
253
- // pnpm-workspace.yaml
242
+ // pnpm-workspace.yaml (all patterns for seamless evolution)
254
243
  writeFile(join(projectDir, 'pnpm-workspace.yaml'), `packages:
255
- - 'packages/*'
244
+ - 'site'
245
+ - 'foundation'
246
+ - 'sites/*'
247
+ - 'foundations/*'
256
248
  `)
257
249
 
258
250
  // .gitignore
@@ -261,11 +253,19 @@ dist
261
253
  .DS_Store
262
254
  *.local
263
255
  `)
256
+ }
257
+
258
+ /**
259
+ * Creates a single project with site/ and foundation/ as sibling packages.
260
+ * This is the default template for new projects.
261
+ */
262
+ async function createSingleProject(projectDir, projectName) {
263
+ createProjectBase(projectDir, projectName, 'site')
264
264
 
265
265
  // README.md
266
266
  writeFile(join(projectDir, 'README.md'), `# ${projectName}
267
267
 
268
- A Uniweb workspace with a site and foundation for co-development.
268
+ Structured Vite + React, ready to go.
269
269
 
270
270
  ## Quick Start
271
271
 
@@ -280,16 +280,21 @@ Open [http://localhost:3000](http://localhost:3000) to see your site.
280
280
 
281
281
  \`\`\`
282
282
  ${projectName}/
283
- ├── packages/
284
- │ ├── site/ # Your website
285
- │ │ ├── pages/ # Content pages (markdown + YAML)
286
- │ │ ├── src/ # Site entry point
287
- │ │ └── vite.config.js
288
- └── foundation/ # Your component library
289
- └── src/
290
- ├── components/ # React components
291
- ├── meta.js # Foundation metadata
292
- │ └── styles.css # Tailwind styles
283
+ ├── site/ # Content + configuration
284
+ │ ├── pages/ # File-based routing
285
+ │ │ └── home/
286
+ │ │ ├── page.yml # Page metadata
287
+ │ │ └── 1-hero.md # Section content
288
+ ├── locales/ # i18n (mirrors pages/)
289
+ ├── src/ # Site entry point
290
+ └── public/ # Static assets
291
+
292
+ ├── foundation/ # Your components
293
+ │ └── src/
294
+ │ ├── components/ # React components
295
+ │ ├── meta.js # Foundation metadata
296
+ │ └── styles.css # Tailwind styles
297
+
293
298
  ├── package.json
294
299
  └── pnpm-workspace.yaml
295
300
  \`\`\`
@@ -302,8 +307,8 @@ ${projectName}/
302
307
  pnpm dev
303
308
  \`\`\`
304
309
 
305
- Edit components in \`packages/foundation/src/components/\` and see changes instantly via HMR.
306
- Edit content in \`packages/site/pages/\` to add or modify pages.
310
+ Edit components in \`foundation/src/components/\` and see changes instantly via HMR.
311
+ Edit content in \`site/pages/\` to add or modify pages.
307
312
 
308
313
  ### Runtime Loading Mode
309
314
 
@@ -317,31 +322,23 @@ Use this to debug issues that only appear in production.
317
322
  ## Building for Production
318
323
 
319
324
  \`\`\`bash
320
- # Build all packages (foundations and sites)
321
325
  pnpm build
322
-
323
- # Build a specific package
324
- pnpm --filter foundation build
325
- pnpm --filter site build
326
-
327
- # Build only certain packages
328
- pnpm --filter marketing-site --filter docs-site build
329
326
  \`\`\`
330
327
 
331
328
  **Output:**
332
- - \`packages/[foundation]/dist/\` — Bundled components, CSS, and schema.json
333
- - \`packages/[site]/dist/\` — Production-ready static site
329
+ - \`foundation/dist/\` — Bundled components, CSS, and schema.json
330
+ - \`site/dist/\` — Production-ready static site
334
331
 
335
332
  ## Adding Components
336
333
 
337
- 1. Create a new folder in \`packages/foundation/src/components/YourComponent/\`
334
+ 1. Create a new folder in \`foundation/src/components/YourComponent/\`
338
335
  2. Add \`index.jsx\` with your React component
339
336
  3. Add \`meta.js\` describing the component's content slots and options
340
- 4. Export from \`packages/foundation/src/index.js\`
337
+ 4. Export from \`foundation/src/index.js\`
341
338
 
342
339
  ## Adding Pages
343
340
 
344
- 1. Create a folder in \`packages/site/pages/your-page/\`
341
+ 1. Create a folder in \`site/pages/your-page/\`
345
342
  2. Add \`page.yml\` with page metadata
346
343
  3. Add markdown files (\`1-section.md\`, \`2-section.md\`, etc.) for each section
347
344
 
@@ -350,98 +347,197 @@ Each markdown file specifies which component to use:
350
347
  \`\`\`markdown
351
348
  ---
352
349
  component: Hero
353
- title: Your Title
354
- subtitle: Your subtitle
355
350
  ---
356
351
 
357
- Optional body content here.
352
+ # Your Title
353
+
354
+ Your subtitle or description here.
355
+
356
+ [Get Started](#)
358
357
  \`\`\`
359
358
 
360
- ## Configuration
359
+ ## Scaling Up
361
360
 
362
- Each site has a \`site.yml\` that configures which foundation it uses:
361
+ The workspace is pre-configured for growth—no config changes needed.
363
362
 
364
- \`\`\`yaml
365
- name: site
366
- defaultLanguage: en
367
- foundation: foundation # References packages/foundation
363
+ **Add a second site** (keep existing structure):
364
+
365
+ \`\`\`bash
366
+ mkdir -p sites/docs
367
+ # Create your docs site in sites/docs/
368
+ \`\`\`
369
+
370
+ **Or migrate to multi-site structure**:
371
+
372
+ \`\`\`bash
373
+ # Move and rename by purpose
374
+ mv site sites/marketing
375
+ mv foundation foundations/marketing
376
+
377
+ # Update package names in package.json files
378
+ # Update dependencies to reference new names
368
379
  \`\`\`
369
380
 
370
- ## Multiple Sites and Foundations
381
+ Both patterns work simultaneously—evolve gradually as needed.
371
382
 
372
- This workspace supports multiple sites and foundations. The \`packages/*\` pattern includes any package you add.
383
+ ## Publishing Your Foundation
373
384
 
374
- **Adding another foundation:**
385
+ Your \`foundation/\` is already a complete package:
375
386
 
376
387
  \`\`\`bash
377
- npx uniweb create packages/docs-foundation --template foundation
388
+ cd foundation
389
+ npx uniweb build
390
+ npm publish
378
391
  \`\`\`
379
392
 
380
- **Adding another site:**
393
+ ## What is Uniweb?
394
+
395
+ Uniweb is a **Component Web Platform** that bridges content and components.
396
+ Foundations define the vocabulary (available components, options, design rules).
397
+ Sites provide content that flows through Foundations.
398
+
399
+ Learn more:
400
+ - [Uniweb on GitHub](https://github.com/uniweb)
401
+ - [CLI Documentation](https://github.com/uniweb/cli)
402
+ - [uniweb.app](https://uniweb.app) — Visual editing platform
403
+
404
+ `)
405
+
406
+ // Create site package
407
+ await createSite(join(projectDir, 'site'), 'site', true)
408
+
409
+ // Create foundation package
410
+ await createFoundation(join(projectDir, 'foundation'), 'foundation', true)
411
+
412
+ // Update site to reference workspace foundation
413
+ const sitePackageJson = join(projectDir, 'site/package.json')
414
+ const sitePkg = JSON.parse(readFile(sitePackageJson))
415
+ sitePkg.dependencies['foundation'] = 'workspace:*'
416
+ writeJSON(sitePackageJson, sitePkg)
417
+
418
+ success(`Created project: ${projectName}`)
419
+ }
420
+
421
+ /**
422
+ * Creates a multi-site/foundation workspace with sites/ and foundations/ directories.
423
+ * Used for larger projects with multiple sites sharing foundations.
424
+ */
425
+ async function createMultiProject(projectDir, projectName) {
426
+ createProjectBase(projectDir, projectName, 'marketing')
427
+
428
+ // README.md
429
+ writeFile(join(projectDir, 'README.md'), `# ${projectName}
430
+
431
+ A Uniweb workspace for multiple sites and foundations.
432
+
433
+ ## Quick Start
381
434
 
382
435
  \`\`\`bash
383
- npx uniweb create packages/docs-site --template site
436
+ pnpm install
437
+ pnpm dev
384
438
  \`\`\`
385
439
 
386
- Then edit \`packages/docs-site/site.yml\` to use the new foundation:
440
+ Open [http://localhost:3000](http://localhost:3000) to see the marketing site.
387
441
 
388
- \`\`\`yaml
389
- foundation: docs-foundation
442
+ ## Project Structure
443
+
444
+ \`\`\`
445
+ ${projectName}/
446
+ ├── sites/
447
+ │ └── marketing/ # Main marketing site
448
+ │ ├── pages/ # Content pages
449
+ │ ├── locales/ # i18n
450
+ │ └── src/
451
+
452
+ ├── foundations/
453
+ │ └── marketing/ # Marketing foundation
454
+ │ └── src/
455
+ │ ├── components/ # React components
456
+ │ └── styles.css # Tailwind styles
457
+
458
+ ├── package.json
459
+ └── pnpm-workspace.yaml
390
460
  \`\`\`
391
461
 
392
- **Running a specific site:**
462
+ ## Development
393
463
 
394
464
  \`\`\`bash
395
- pnpm --filter docs-site dev
465
+ # Run the marketing site (default)
466
+ pnpm dev
467
+
468
+ # Run a specific site
469
+ pnpm --filter docs dev
470
+
471
+ # Runtime loading mode (tests production behavior)
472
+ pnpm dev:runtime
396
473
  \`\`\`
397
474
 
398
- ## Sharing Components
475
+ ## Adding More Sites
399
476
 
400
- For components shared across multiple foundations, create a shared package:
477
+ Create a new site in \`sites/\`:
401
478
 
479
+ \`\`\`bash
480
+ mkdir -p sites/docs
481
+ # Copy structure from sites/marketing or create manually
402
482
  \`\`\`
403
- packages/
404
- ├── shared/ # Shared React components
405
- │ ├── package.json
406
- │ └── src/
407
- │ └── Button.jsx
408
- ├── marketing-foundation/
409
- ├── docs-foundation/
410
- └── site/
483
+
484
+ Update \`sites/docs/site.yml\` to specify which foundation:
485
+
486
+ \`\`\`yaml
487
+ name: docs
488
+ defaultLanguage: en
489
+ foundation: documentation # Or marketing, or any foundation
411
490
  \`\`\`
412
491
 
413
- Foundations can import from the shared package:
492
+ ## Adding More Foundations
414
493
 
415
- \`\`\`js
416
- // packages/marketing-foundation/src/components/Hero/index.jsx
417
- import { Button } from 'shared'
494
+ Create a new foundation in \`foundations/\`:
495
+
496
+ \`\`\`bash
497
+ mkdir -p foundations/documentation
498
+ # Build components for documentation use case
418
499
  \`\`\`
419
500
 
420
- ## What is Uniweb?
501
+ Name foundations by purpose: marketing, documentation, learning, etc.
421
502
 
422
- Uniweb is a **Component Web Platform** that bridges content and components.
423
- Foundations define the vocabulary (available components, options, design rules).
424
- Sites provide content that flows through Foundations.
503
+ ## Building for Production
504
+
505
+ \`\`\`bash
506
+ # Build everything
507
+ pnpm build
508
+
509
+ # Build specific packages
510
+ pnpm --filter marketing build
511
+ pnpm --filter foundations/marketing build
512
+ \`\`\`
513
+
514
+ ## Learn More
425
515
 
426
- Learn more:
427
516
  - [Uniweb on GitHub](https://github.com/uniweb)
428
- - [CLI Documentation](https://github.com/uniweb/cli)
429
- - [uniweb.app](https://uniweb.app) — Full publishing platform
517
+ - [uniweb.app](https://uniweb.app) — Visual editing platform
430
518
 
431
519
  `)
432
520
 
433
- // Create site package
434
- await createSite(join(projectDir, 'packages/site'), 'site', true)
521
+ // Create first site in sites/marketing
522
+ await createSite(join(projectDir, 'sites/marketing'), 'marketing', true)
435
523
 
436
- // Create foundation package
437
- await createFoundation(join(projectDir, 'packages/foundation'), 'foundation', true)
524
+ // Create first foundation in foundations/marketing
525
+ await createFoundation(join(projectDir, 'foundations/marketing'), 'marketing', true)
438
526
 
439
527
  // Update site to reference workspace foundation
440
- const sitePackageJson = join(projectDir, 'packages/site/package.json')
528
+ const sitePackageJson = join(projectDir, 'sites/marketing/package.json')
441
529
  const sitePkg = JSON.parse(readFile(sitePackageJson))
442
- sitePkg.dependencies['foundation'] = 'workspace:*'
530
+ sitePkg.dependencies['marketing'] = 'workspace:*'
443
531
  writeJSON(sitePackageJson, sitePkg)
444
532
 
533
+ // Update site.yml to reference the marketing foundation
534
+ writeFile(join(projectDir, 'sites/marketing/site.yml'), `name: marketing
535
+ defaultLanguage: en
536
+
537
+ # Foundation to use for this site
538
+ foundation: marketing
539
+ `)
540
+
445
541
  success(`Created workspace: ${projectName}`)
446
542
  }
447
543
 
@@ -623,13 +719,13 @@ order: 1
623
719
  // pages/home/1-hero.md
624
720
  writeFile(join(projectDir, 'pages/home/1-hero.md'), `---
625
721
  component: Hero
626
- title: Welcome to ${projectName}
627
- subtitle: Built with Uniweb and Vite
628
- ctaText: Get Started
629
- ctaUrl: "#"
630
722
  ---
631
723
 
632
- Your content goes here.
724
+ # Welcome to ${projectName}
725
+
726
+ Built with Uniweb and Vite.
727
+
728
+ [Get Started](#)
633
729
  `)
634
730
 
635
731
  // README.md (only for standalone site, not workspace)
@@ -677,19 +773,22 @@ order: 2
677
773
  \`\`\`markdown
678
774
  ---
679
775
  component: Hero
680
- title: Section Title
681
- subtitle: Section subtitle
776
+ theme: dark
682
777
  ---
683
778
 
684
- Optional markdown content here.
779
+ # Section Title
780
+
781
+ Section description here.
782
+
783
+ [Call to Action](#)
685
784
  \`\`\`
686
785
 
687
786
  ## How It Works
688
787
 
689
788
  - Each folder in \`pages/\` becomes a route (\`/home\`, \`/about\`, etc.)
690
789
  - Section files are numbered to control order (\`1-*.md\`, \`2-*.md\`)
691
- - The \`component\` field specifies which Foundation component renders the section
692
- - Other frontmatter fields become the component's content
790
+ - Frontmatter specifies the component and configuration parameters
791
+ - Content in the markdown body is semantically parsed into structured data
693
792
 
694
793
  ## Configuration
695
794
 
@@ -901,22 +1000,36 @@ export { default } from './index.js'
901
1000
  // src/components/Hero/index.jsx
902
1001
  writeFile(join(projectDir, 'src/components/Hero/index.jsx'), `import React from 'react'
903
1002
 
904
- export function Hero({ content }) {
905
- const { title, subtitle, ctaText, ctaUrl } = content
1003
+ export function Hero({ content, params }) {
1004
+ // Extract from semantic parser structure
1005
+ const { title } = content.main?.header || {}
1006
+ const { paragraphs = [], links = [] } = content.main?.body || {}
1007
+ const { theme = 'light' } = params || {}
1008
+
1009
+ const isDark = theme === 'dark'
1010
+ const cta = links[0]
906
1011
 
907
1012
  return (
908
- <section className="py-20 px-6 bg-gradient-to-br from-primary to-blue-700 text-white">
1013
+ <section className={\`py-20 px-6 \${isDark ? 'bg-gradient-to-br from-primary to-blue-700 text-white' : 'bg-gray-50'}\`}>
909
1014
  <div className="max-w-4xl mx-auto text-center">
910
- <h1 className="text-4xl md:text-5xl font-bold mb-6">{title}</h1>
911
- {subtitle && (
912
- <p className="text-lg text-blue-100 mb-8">{subtitle}</p>
1015
+ {title && (
1016
+ <h1 className="text-4xl md:text-5xl font-bold mb-6">{title}</h1>
1017
+ )}
1018
+ {paragraphs[0] && (
1019
+ <p className={\`text-lg mb-8 \${isDark ? 'text-blue-100' : 'text-gray-600'}\`}>
1020
+ {paragraphs[0]}
1021
+ </p>
913
1022
  )}
914
- {ctaText && ctaUrl && (
1023
+ {cta && (
915
1024
  <a
916
- href={ctaUrl}
917
- className="inline-block px-6 py-3 bg-white text-primary font-semibold rounded-lg hover:bg-blue-50 transition-colors"
1025
+ href={cta.url}
1026
+ className={\`inline-block px-6 py-3 font-semibold rounded-lg transition-colors \${
1027
+ isDark
1028
+ ? 'bg-white text-primary hover:bg-blue-50'
1029
+ : 'bg-primary text-white hover:bg-blue-700'
1030
+ }\`}
918
1031
  >
919
- {ctaText}
1032
+ {cta.text}
920
1033
  </a>
921
1034
  )}
922
1035
  </div>
@@ -930,26 +1043,45 @@ export default Hero
930
1043
  // src/components/Hero/meta.js
931
1044
  writeFile(join(projectDir, 'src/components/Hero/meta.js'), `/**
932
1045
  * Hero Component Metadata
1046
+ *
1047
+ * Content comes from the markdown body (parsed semantically):
1048
+ * - H1 → title (content.main.header.title)
1049
+ * - Paragraphs → description (content.main.body.paragraphs)
1050
+ * - Links → CTA buttons (content.main.body.links)
933
1051
  */
934
1052
  export default {
935
1053
  title: 'Hero Banner',
936
- description: 'A prominent header section with headline, subtitle, and call-to-action',
1054
+ description: 'A prominent header section with headline, description, and call-to-action',
937
1055
  category: 'Headers',
938
1056
 
1057
+ // Content structure (informational - describes what the semantic parser provides)
939
1058
  elements: {
940
1059
  title: {
941
1060
  label: 'Headline',
1061
+ description: 'From H1 in markdown',
942
1062
  required: true,
943
1063
  },
944
- subtitle: {
945
- label: 'Subtitle',
1064
+ paragraphs: {
1065
+ label: 'Description',
1066
+ description: 'From paragraphs in markdown',
946
1067
  },
947
1068
  links: {
948
1069
  label: 'Call to Action',
1070
+ description: 'From links in markdown',
949
1071
  },
950
1072
  },
951
1073
 
1074
+ // Configuration parameters (set in frontmatter)
952
1075
  properties: {
1076
+ theme: {
1077
+ type: 'select',
1078
+ label: 'Theme',
1079
+ options: [
1080
+ { value: 'light', label: 'Light' },
1081
+ { value: 'dark', label: 'Dark' },
1082
+ ],
1083
+ default: 'light',
1084
+ },
953
1085
  alignment: {
954
1086
  type: 'select',
955
1087
  label: 'Text Alignment',