uniweb 0.8.14 → 0.8.16

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 CHANGED
@@ -36,10 +36,10 @@ Edit files in `site/pages/` and `foundation/src/sections/` to see changes instan
36
36
  | **Store** | E-commerce with product grid |
37
37
  | **Extensions** | Multi-foundation with visual effects extension |
38
38
 
39
- Use `--blank` for an empty workspace (no packages) — grow with `uniweb add`.
40
-
41
39
  **See them live:** [View all template demos](https://uniweb.github.io/templates/)
42
40
 
41
+ Use `--blank` for an empty workspace (no packages) — grow with `uniweb add`.
42
+
43
43
  Or skip the interactive prompt:
44
44
 
45
45
  ```bash
@@ -164,13 +164,43 @@ export default function Hero({ content, params }) {
164
164
 
165
165
  Standard React. Standard Tailwind. The `{ content, params }` interface is only for _section types_ — components that content creators select in markdown frontmatter. Everything else uses regular React props.
166
166
 
167
+ ### Composition
168
+
169
+ Sections aren't limited to flat content. Content authors can embed interactive React components using markdown image syntax:
170
+
171
+ ```markdown
172
+ ---
173
+ type: SplitContent
174
+ ---
175
+
176
+ # See it in action
177
+
178
+ The architecture handles the hard parts so you can focus on what matters.
179
+
180
+ ![Live metrics](@PerformanceChart){period=30d}
181
+ ```
182
+
183
+ `@PerformanceChart` is a full React component — a ThreeJS animation, an interactive diagram, a live data visualization — placed by the content author, rendered in the component's visual slot via `<Visual>`. It looks like an image reference, but it can be anything.
184
+
185
+ Authors can also compose layouts from reusable section types using child sections:
186
+
187
+ ```
188
+ pages/home/
189
+ ├── highlights.md # type: Grid, columns: 3
190
+ ├── @stats.md # type: StatCard
191
+ ├── @testimonial.md # type: Testimonial
192
+ └── @demo.md # type: SplitContent (with an embedded @LiveDemo inset)
193
+ ```
194
+
195
+ Three different section types, arranged in a grid, one with an interactive component inside it — all authored in markdown. The developer builds reusable pieces; the content author composes them. See the [Component Patterns guide](https://github.com/uniweb/docs/blob/main/development/component-patterns.md) for the full composition model.
196
+
167
197
  ## Next Steps
168
198
 
169
199
  After creating your project:
170
200
 
171
201
  1. **Explore the structure** — Browse `site/pages/` to see how content is organized. Each page folder contains `page.yml` (metadata) and `.md` files (sections).
172
202
 
173
- 2. **Generate component docs** — Run `npx uniweb docs` to create `COMPONENTS.md` with all available components, their parameters, and presets.
203
+ 2. **Generate component docs** — Run `uniweb docs` to create `COMPONENTS.md` with all available components, their parameters, and presets.
174
204
 
175
205
  3. **Learn the configuration** — Run `uniweb docs site` or `uniweb docs page` for quick reference on configuration options.
176
206
 
@@ -237,31 +267,31 @@ Start simple. Add what you need, when you need it:
237
267
  cd my-site
238
268
 
239
269
  # Add a co-located foundation + site pair
240
- npx uniweb add project blog
270
+ uniweb add project blog
241
271
 
242
272
  # Add a second foundation at root
243
- npx uniweb add foundation ui
273
+ uniweb add foundation ui
244
274
 
245
275
  # Add a site wired to a specific foundation
246
- npx uniweb add site docs --foundation ui
276
+ uniweb add site docs --foundation ui
247
277
 
248
278
  # Add an extension (auto-wired to the only site)
249
- npx uniweb add extension effects
279
+ uniweb add extension effects
250
280
 
251
281
  # Scaffold + apply content from an official template
252
- npx uniweb add project marketing --from marketing
282
+ uniweb add project marketing --from marketing
253
283
  ```
254
284
 
255
285
  The workspace grows organically. `add` handles placement, wires dependencies, updates workspace globs, and generates root scripts. The name you provide becomes both the directory name and the package name. Use `--path` to override default placement, or `--project` for explicit co-located layouts.
256
286
 
257
- > `npx uniweb` works before and after install. Once dependencies are installed, you can also use `pnpm uniweb` directly since `uniweb` is a project dependency.
287
+ > **Install the CLI globally** with `npm i -g uniweb` for the best experience. You can also use `npx uniweb` or `pnpm uniweb` without a global install.
258
288
 
259
289
  **Or start blank and build up:**
260
290
 
261
291
  ```bash
262
292
  pnpm create uniweb acme --blank
263
293
  cd acme
264
- npx uniweb add project main
294
+ uniweb add project main
265
295
  pnpm install
266
296
  pnpm dev
267
297
  ```
@@ -386,7 +416,7 @@ packages:
386
416
 
387
417
  **How is this different from MDX?**
388
418
 
389
- 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.
419
+ MDX blends markdown and JSX—content authors write code. Uniweb keeps them separate: content stays in markdown, components stay in React. Authors can still embed interactive components (via `![](@Component)` syntax), but they never see JSX. Component updates don't require content changes, and content changes can't break components.
390
420
 
391
421
  **How is this different from Astro?**
392
422
 
@@ -420,4 +450,4 @@ Yes — documentation is a natural fit. Content stays in markdown (easy to versi
420
450
 
421
451
  ## License
422
452
 
423
- Apache 2.0
453
+ Apache 2.0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.8.14",
3
+ "version": "0.8.16",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,11 +41,24 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/build": "0.8.13",
45
- "@uniweb/content-reader": "1.1.4",
46
- "@uniweb/kit": "0.7.10",
47
- "@uniweb/runtime": "0.6.10",
44
+ "@uniweb/runtime": "0.6.11",
48
45
  "@uniweb/core": "0.5.10",
49
- "@uniweb/semantic-parser": "1.1.6"
46
+ "@uniweb/kit": "0.7.11"
47
+ },
48
+ "peerDependencies": {
49
+ "@uniweb/build": "0.8.15",
50
+ "@uniweb/semantic-parser": "1.1.6",
51
+ "@uniweb/content-reader": "1.1.4"
52
+ },
53
+ "peerDependenciesMeta": {
54
+ "@uniweb/build": {
55
+ "optional": true
56
+ },
57
+ "@uniweb/content-reader": {
58
+ "optional": true
59
+ },
60
+ "@uniweb/semantic-parser": {
61
+ "optional": true
62
+ }
50
63
  }
51
64
  }
@@ -83,7 +83,7 @@ Use `--template <n>` for an official template (`none`, `starter`, `marketing`, `
83
83
  ### Adding a co-located project
84
84
 
85
85
  ```bash
86
- pnpm uniweb add project docs
86
+ uniweb add project docs
87
87
  pnpm install
88
88
  ```
89
89
 
@@ -92,10 +92,10 @@ This creates `docs/foundation/` + `docs/site/` with package names `docs-foundati
92
92
  ### Adding individual packages
93
93
 
94
94
  ```bash
95
- pnpm uniweb add foundation # First foundation → ./foundation/
96
- pnpm uniweb add foundation ui # Named → ./ui/
97
- pnpm uniweb add site # First site → ./site/
98
- pnpm uniweb add site blog # Named → ./blog/
95
+ uniweb add foundation # First foundation → ./foundation/
96
+ uniweb add foundation ui # Named → ./ui/
97
+ uniweb add site # First site → ./site/
98
+ uniweb add site blog # Named → ./blog/
99
99
  ```
100
100
 
101
101
  The name is both the directory name and the package name. Use `--project <n>` to co-locate under a project directory (e.g., `--project docs` → `docs/foundation/`).
@@ -103,8 +103,8 @@ The name is both the directory name and the package name. Use `--project <n>` to
103
103
  ### Adding section types
104
104
 
105
105
  ```bash
106
- pnpm uniweb add section Hero
107
- pnpm uniweb add section Hero --foundation ui # When multiple foundations exist
106
+ uniweb add section Hero
107
+ uniweb add section Hero --foundation ui # When multiple foundations exist
108
108
  ```
109
109
 
110
110
  Creates `src/sections/Hero/index.jsx` and `meta.js` with a minimal CCA-proper starter. The dev server picks it up automatically — no build or install needed.
@@ -340,9 +340,9 @@ Check out [this](/a) link. ← inline → stays in paragraphs as <a> tag
340
340
  This is [less important]{muted} context.
341
341
  ```
342
342
 
343
- `accent` (colored + bold) and `muted` (subtle) adapt to context automatically. Components receive HTML strings with spans applied: `<span accent="true">faster</span>`.
343
+ `accent` (link-colored + bold), `callout` (accent-colored + bold), and `muted` (subtle) are built-in defaults that adapt to context automatically. Components receive HTML strings with spans applied: `<span accent="true">faster</span>`.
344
344
 
345
- Sites can define additional named styles in `theme.yml`'s `inline:` section.
345
+ Sites can override these or define additional named styles in `theme.yml`'s `inline:` section.
346
346
 
347
347
  ### Fenced Code in Content
348
348
 
@@ -676,6 +676,9 @@ inline:
676
676
  accent:
677
677
  color: var(--link)
678
678
  font-weight: '600'
679
+ callout:
680
+ color: var(--accent)
681
+ font-weight: '600'
679
682
 
680
683
  vars:
681
684
  header-height: 5rem
@@ -1282,9 +1285,9 @@ Semantic tokens come from `theme-tokens.css` (populated from `theme.yml`). Use `
1282
1285
 
1283
1286
  **Content not appearing as expected?**
1284
1287
  ```bash
1285
- pnpm uniweb inspect pages/home/hero.md # Single section
1286
- pnpm uniweb inspect pages/home/ # Whole page
1287
- pnpm uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1288
+ uniweb inspect pages/home/hero.md # Single section
1289
+ uniweb inspect pages/home/ # Whole page
1290
+ uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1288
1291
  ```
1289
1292
 
1290
1293
  ## Learning from Official Templates
@@ -1292,7 +1295,7 @@ pnpm uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1292
1295
  When you're unsure how to implement a pattern — data fetching, i18n, layouts, insets, theming — install an official template as a reference project in your workspace:
1293
1296
 
1294
1297
  ```bash
1295
- pnpm uniweb add project marketing --from marketing
1298
+ uniweb add project marketing --from marketing
1296
1299
  pnpm install
1297
1300
  ```
1298
1301
 
@@ -4,10 +4,10 @@ Generate up-to-date documentation for all foundation components:
4
4
 
5
5
  ```bash
6
6
  # From foundation directory
7
- pnpm uniweb docs
7
+ uniweb docs
8
8
 
9
9
  # Or from site directory (auto-detects linked foundation)
10
- cd site && pnpm uniweb docs
10
+ cd site && uniweb docs
11
11
  ```
12
12
 
13
13
  This creates `COMPONENTS.md` with details on each component's parameters, presets, and content elements. The documentation is generated from component `meta.js` files, so it's always current.
@@ -64,4 +64,4 @@ seo:
64
64
  priority: 0.8
65
65
  ```
66
66
 
67
- Run `pnpm uniweb docs site` or `pnpm uniweb docs page` for complete reference.
67
+ Run `uniweb docs site` or `uniweb docs page` for complete reference.
@@ -121,7 +121,7 @@ export async function add(rawArgs) {
121
121
  error(`Missing subcommand.\n`)
122
122
  log(formatOptions([
123
123
  { label: 'project', description: 'Co-located foundation + site pair' },
124
- { label: 'foundation', description: 'Component library' },
124
+ { label: 'foundation', description: 'Component system for content authors' },
125
125
  { label: 'site', description: 'Content site' },
126
126
  { label: 'extension', description: 'Additional component package' },
127
127
  { label: 'section', description: 'Section type in a foundation' },
@@ -137,7 +137,7 @@ export async function add(rawArgs) {
137
137
  message: 'What would you like to add?',
138
138
  choices: [
139
139
  { title: 'Project', value: 'project', description: 'Co-located foundation + site pair' },
140
- { title: 'Foundation', value: 'foundation', description: 'Component library' },
140
+ { title: 'Foundation', value: 'foundation', description: 'Component system for content authors' },
141
141
  { title: 'Site', value: 'site', description: 'Content site' },
142
142
  { title: 'Extension', value: 'extension', description: 'Additional component package' },
143
143
  { title: 'Section', value: 'section', description: 'Section type in a foundation' },
@@ -352,7 +352,7 @@ async function addSite(rootDir, projectName, opts, pm = 'pnpm') {
352
352
  }, {
353
353
  onProgress: (msg) => info(` ${msg}`),
354
354
  })
355
- log(` ${colors.yellow}⚠ No foundation wired. Add one later with: npx uniweb add foundation${colors.reset}`)
355
+ log(` ${colors.yellow}⚠ No foundation wired. Add one later with: uniweb add foundation${colors.reset}`)
356
356
  }
357
357
 
358
358
  // Apply template content if --from specified
@@ -594,7 +594,8 @@ async function addProject(rootDir, projectName, opts, pm = 'pnpm') {
594
594
  * - --project: {project}/foundation (co-located)
595
595
  * - Existing co-located glob: follow pattern
596
596
  * - Existing segregated glob: follow pattern
597
- * - First foundation: dir name is the name (default: 'foundation')
597
+ * - Named (e.g., "marketing"): segregated at foundations/{name}
598
+ * - Unnamed: root-level 'foundation'
598
599
  * - Already have one: error in non-interactive, ask in interactive
599
600
  */
600
601
  async function resolveFoundationTarget(rootDir, name, opts) {
@@ -619,8 +620,13 @@ async function resolveFoundationTarget(rootDir, name, opts) {
619
620
  return `foundations/${name || 'foundation'}`
620
621
  }
621
622
 
622
- // dir name = name or 'foundation'
623
- const dirName = name || 'foundation'
623
+ // Named foundation segregated layout (foundations/{name})
624
+ if (name) {
625
+ return `foundations/${name}`
626
+ }
627
+
628
+ // Unnamed → root-level 'foundation'
629
+ const dirName = 'foundation'
624
630
 
625
631
  // Check if target already exists
626
632
  if (!existsSync(join(rootDir, dirName))) {
@@ -667,8 +673,13 @@ async function resolveSiteTarget(rootDir, name, opts) {
667
673
  return `sites/${name || 'site'}`
668
674
  }
669
675
 
670
- // dir name = name or 'site'
671
- const dirName = name || 'site'
676
+ // Named site segregated layout (sites/{name})
677
+ if (name) {
678
+ return `sites/${name}`
679
+ }
680
+
681
+ // Unnamed → root-level 'site'
682
+ const dirName = 'site'
672
683
 
673
684
  // Check if target already exists
674
685
  if (!existsSync(join(rootDir, dirName))) {
@@ -1050,9 +1061,9 @@ ${colors.bright}Examples:${colors.reset}
1050
1061
  uniweb add project docs # Create docs/foundation/ + docs/site/
1051
1062
  uniweb add project docs --from academic # Co-located pair + academic content
1052
1063
  uniweb add foundation # Create ./foundation/ at root
1053
- uniweb add foundation ui # Create ./ui/ at root
1064
+ uniweb add foundation ui # Create ./foundations/ui/
1054
1065
  uniweb add site # Create ./site/ at root
1055
- uniweb add site blog --foundation marketing # Create ./blog/ wired to marketing
1066
+ uniweb add site blog --foundation marketing # Create ./sites/blog/ wired to marketing
1056
1067
  uniweb add extension effects --site site # Create ./extensions/effects/
1057
1068
  uniweb add section Hero # Create Hero section type
1058
1069
  uniweb add section Hero --foundation ui # Target specific foundation
@@ -18,9 +18,7 @@ import { writeFile, mkdir } from 'node:fs/promises'
18
18
  // Import build utilities from @uniweb/build
19
19
  import {
20
20
  generateEntryPoint,
21
- buildSchema,
22
21
  discoverComponents,
23
- processAllPreviews,
24
22
  } from '@uniweb/build'
25
23
  import { readSiteConfig } from '@uniweb/build/site'
26
24
  import { readWorkspaceConfig, resolveGlob } from '../utils/config.js'
@@ -130,7 +128,6 @@ function runCommand(command, args, cwd) {
130
128
  */
131
129
  async function buildFoundation(projectDir, options = {}) {
132
130
  const srcDir = join(projectDir, 'src')
133
- const distDir = join(projectDir, 'dist')
134
131
 
135
132
  info('Building foundation...')
136
133
 
@@ -163,50 +160,19 @@ async function buildFoundation(projectDir, options = {}) {
163
160
  // For now, just run the standard vite build
164
161
  await runCommand('npx', ['vite', 'build'], projectDir)
165
162
 
166
- success('Vite build complete')
167
-
168
- // 4. Generate schema.json
169
- log('')
170
- info('Generating schema.json...')
171
- let schema = await buildSchema(srcDir)
172
-
173
- await mkdir(distDir, { recursive: true })
163
+ // Vite's foundation plugin generates dist/meta/schema.json
164
+ // and processes preview images during the build.
174
165
 
175
- // 5. Process preview images
176
- log('')
177
- info('Processing preview images...')
178
- const isProduction = process.env.NODE_ENV === 'production' || !process.env.NODE_ENV
179
- const { schema: updatedSchema, totalImages } = await processAllPreviews(
180
- srcDir,
181
- distDir,
182
- schema,
183
- isProduction
184
- )
185
- schema = updatedSchema
186
-
187
- if (totalImages > 0) {
188
- success(`Processed ${totalImages} preview image${totalImages > 1 ? 's' : ''} (converted to webp)`)
189
- } else {
190
- log(` ${colors.dim}No preview images found${colors.reset}`)
191
- }
192
-
193
- // 6. Write schema.json
194
- const schemaPath = join(distDir, 'schema.json')
195
- await writeFile(schemaPath, JSON.stringify(schema, null, 2), 'utf-8')
196
-
197
- success(`Generated schema.json with ${componentNames.length} components`)
166
+ success('Vite build complete')
198
167
 
199
168
  // Summary
200
169
  log('')
201
170
  log(`${colors.green}${colors.bright}Build complete!${colors.reset}`)
171
+
202
172
  log('')
203
- log(`Output:`)
204
- log(` ${colors.dim}dist/foundation.js${colors.reset} - Bundled components`)
205
- log(` ${colors.dim}dist/assets/style.css${colors.reset} - Compiled CSS`)
206
- log(` ${colors.dim}dist/schema.json${colors.reset} - Component schemas`)
207
- if (totalImages > 0) {
208
- log(` ${colors.dim}dist/assets/[component]/${colors.reset} - Preview images`)
209
- }
173
+ log(`${colors.bright}Share with clients:${colors.reset}`)
174
+ log(` ${colors.bright}uniweb publish${colors.reset} Register your foundation (one-time setup)`)
175
+ log(` ${colors.bright}uniweb handoff <email>${colors.reset} Hand off a site to a client`)
210
176
  }
211
177
 
212
178
  /**
@@ -489,6 +455,8 @@ async function buildSite(projectDir, options = {}) {
489
455
  process.exit(1)
490
456
  }
491
457
  }
458
+
459
+ showNextSteps(false, true)
492
460
  }
493
461
 
494
462
  /**
@@ -625,6 +593,26 @@ async function buildWorkspace(workspaceDir, options = {}) {
625
593
  log(`${colors.green}${colors.bright}Workspace build complete!${colors.reset}`)
626
594
  log('')
627
595
  log(`Built ${parts.join(', ')}`)
596
+
597
+ showNextSteps(foundations.length > 0, sites.length > 0)
598
+ }
599
+
600
+ /**
601
+ * Show next-step hints after workspace build
602
+ */
603
+ function showNextSteps(hasFoundations, hasSites) {
604
+ if (hasFoundations) {
605
+ log('')
606
+ log(`${colors.bright}Share with clients:${colors.reset}`)
607
+ log(` ${colors.bright}uniweb publish${colors.reset} Register your foundation (one-time setup)`)
608
+ log(` ${colors.bright}uniweb handoff <email>${colors.reset} Hand off a site to a client`)
609
+ }
610
+ if (hasSites) {
611
+ log('')
612
+ log(`${colors.bright}Deploy:${colors.reset}`)
613
+ log(` ${colors.bright}uniweb deploy${colors.reset} Uniweb hosting`)
614
+ log(` Or upload ${colors.cyan}dist/${colors.reset} to any static host`)
615
+ }
628
616
  }
629
617
 
630
618
  /**