uniweb 0.8.5 → 0.8.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.8.5",
3
+ "version": "0.8.6",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,9 +41,9 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/core": "0.5.4",
44
+ "@uniweb/build": "0.8.5",
45
45
  "@uniweb/kit": "0.7.3",
46
- "@uniweb/runtime": "0.6.4",
47
- "@uniweb/build": "0.8.4"
46
+ "@uniweb/core": "0.5.4",
47
+ "@uniweb/runtime": "0.6.4"
48
48
  }
49
49
  }
@@ -67,8 +67,11 @@ The name is both the directory name and the package name. Use `--project <name>`
67
67
  pnpm install # Install dependencies
68
68
  pnpm dev # Start dev server
69
69
  pnpm build # Build for production
70
+ pnpm preview # Preview production build (SSG + SPA)
70
71
  ```
71
72
 
73
+ > **npm works too.** Projects include both `pnpm-workspace.yaml` and npm workspaces. Replace `pnpm` with `npm` in any command above.
74
+
72
75
  ## Content Authoring
73
76
 
74
77
  ### Section Format
@@ -112,7 +115,7 @@ content = {
112
115
  insets: [], // Inline @Component references — { refId }
113
116
  lists: [], // [[{ paragraphs, links, lists, ... }]] — each list item is an object, not a string
114
117
  quotes: [], // Blockquotes
115
- data: {}, // From tagged code blocks (```yaml:tagname)
118
+ data: {}, // From tagged code blocks (```yaml:tagname) and (```js:tagname)
116
119
  headings: [], // Overflow headings after subtitle2
117
120
  items: [], // Each has the same flat structure — from headings after body content
118
121
  sequence: [], // All elements in document order
@@ -127,12 +130,16 @@ content = {
127
130
  We built this for you. ← paragraph
128
131
 
129
132
  ### Fast ← items[0].title
133
+ ![](lu-zap) ← items[0].icons[0]
130
134
  Lightning quick. ← items[0].paragraphs[0]
131
135
 
132
136
  ### Secure ← items[1].title
137
+ ![](lu-shield) ← items[1].icons[0]
133
138
  Enterprise-grade. ← items[1].paragraphs[0]
134
139
  ```
135
140
 
141
+ Each item has the same content shape as the top level — `title`, `paragraphs`, `icons`, `links`, `lists`, etc. are all available per item.
142
+
136
143
  **Lists** contain bullet or ordered list items. Each list item is an object with the same content shape — not a plain string:
137
144
 
138
145
  ```markdown
@@ -186,6 +193,7 @@ The three parts carry distinct information:
186
193
  ![Architecture diagram](@NetworkDiagram){variant=compact}
187
194
  ![Cache metrics](@PerformanceChart){period=30d}
188
195
  ![](@GradientBlob){position=top-right}
196
+ ![npm create uniweb](@CommandBlock){note="Vite + React + Routing — ready to go"}
189
197
  ```
190
198
 
191
199
  Inset components must declare `inset: true` in their `meta.js`. They render at the exact position in the content flow where the author placed them. See meta.js section below for details.
@@ -198,6 +206,8 @@ Inset components must declare `inset: true` in their `meta.js`. They render at t
198
206
  ![alt](./img.jpg){role=banner} <!-- Role determines array: imgs, icons, or videos -->
199
207
  ```
200
208
 
209
+ **Quote values that contain spaces:** `{note="Ready to go"}` not `{note=Ready to go}`. Unquoted values end at the first space.
210
+
201
211
  Standalone links (alone on a line) become buttons. Inline links stay as text links.
202
212
 
203
213
  ### Structured Data
@@ -215,19 +225,33 @@ submitLabel: Send
215
225
 
216
226
  Access: `content.data?.form` → `{ fields: [...], submitLabel: "Send" }`
217
227
 
228
+ **Code blocks need tags too.** Untagged code blocks (plain ```js) are only visible to sequential-rendering components like Article or DocSection. If a component needs to access code blocks by name, tag them:
229
+
230
+ ````markdown
231
+ ```jsx:before
232
+ const old = fetch('/api')
233
+ ```
234
+
235
+ ```jsx:after
236
+ const data = useData()
237
+ ```
238
+ ````
239
+
240
+ Access: `content.data?.before`, `content.data?.after` → raw code strings.
241
+
218
242
  ### Section Backgrounds
219
243
 
220
- Set `background` in frontmatter — the runtime renders it automatically:
244
+ Set `background` in frontmatter — the runtime renders it automatically. The string form auto-detects the type:
221
245
 
222
246
  ```yaml
223
- ---
224
- type: Hero
225
- theme: dark
226
- background: /images/hero.jpg # Simple: URL (image or video auto-detected)
227
- ---
247
+ background: /images/hero.jpg # Image (by extension)
248
+ background: /videos/hero.mp4 # Video (by extension)
249
+ background: linear-gradient(135deg, #667eea, #764ba2) # CSS gradient
250
+ background: '#1a1a2e' # Color (hex quote in YAML)
251
+ background: var(--primary-900) # Color (CSS variable)
228
252
  ```
229
253
 
230
- Full syntax supports `image`, `video`, `gradient`, `color` modes plus overlays:
254
+ The object form gives more control:
231
255
 
232
256
  ```yaml
233
257
  background:
@@ -235,6 +259,8 @@ background:
235
259
  overlay: { enabled: true, type: dark, opacity: 0.5 }
236
260
  ```
237
261
 
262
+ Overlay shorthand — `overlay: 0.5` is equivalent to `{ enabled: true, type: dark, opacity: 0.5 }`.
263
+
238
264
  Components that render their own background declare `background: 'self'` in `meta.js`.
239
265
 
240
266
  ### Page Organization
@@ -417,7 +443,7 @@ The runtime does significant work that other frameworks push onto components. Un
417
443
  | `isDark ? 'text-white' : 'text-gray-900'` | Just write `text-heading` — it adapts |
418
444
  | Background rendering code | Declare `background:` in frontmatter instead |
419
445
  | Color constants / tokens files | Colors come from `theme.yml` |
420
- | Custom CSS variables for colors (`--ink`, `--paper`, `--accent`) in `styles.css` | Map source colors to `theme.yml` colors/neutral. The build generates `--primary-50` through `--primary-950`, `--neutral-50` through `--neutral-950`, etc. Components use semantic tokens (`text-heading`, `bg-section`) that resolve from these palettes per context. A parallel color system bypasses all of this. |
446
+ | Parallel color system (`--ink`, `--paper`) that duplicates what tokens already provide | Map source color roles to `theme.yml` colors/neutral. The build generates `--primary-50` through `--primary-950`, `--neutral-50` through `--neutral-950`, etc. Use palette shades directly (`var(--primary-300)`) for specific tones. Additive design classes that BUILD ON tokens are fine a parallel system that REPLACES them bypasses context adaptation. |
421
447
 
422
448
  **What to hardcode** (not semantic — same in every context): layout (`grid`, `flex`, `max-w-6xl`), spacing (`p-6`, `gap-8`), typography scale (`text-3xl`, `font-bold`), animations, border-radius.
423
449
 
@@ -430,6 +456,33 @@ theme: dark ← sets context-dark, all tokens resolve to dark values
430
456
  ---
431
457
  ```
432
458
 
459
+ Alternate between `light` (default), `medium`, and `dark` across sections for visual rhythm — no CSS needed. A typical marketing page:
460
+
461
+ ```markdown
462
+ <!-- 1-hero.md -->
463
+ theme: dark
464
+
465
+ <!-- 2-features.md -->
466
+ (no theme — defaults to light)
467
+
468
+ <!-- 3-testimonials.md -->
469
+ theme: medium
470
+
471
+ <!-- 4-cta.md -->
472
+ theme: dark
473
+ ```
474
+
475
+ **Per-section token overrides** — the object form lets authors fine-tune individual tokens for a specific section:
476
+
477
+ ```yaml
478
+ theme:
479
+ mode: light
480
+ primary: var(--neutral-900) # Dark buttons in a light section
481
+ primary-hover: var(--neutral-800)
482
+ ```
483
+
484
+ Any semantic token (`section`, `heading`, `body`, `primary`, `link`, etc.) can be overridden this way. The overrides are applied as inline CSS custom properties on the section wrapper — components don't need to know about them.
485
+
433
486
  **Site controls the palette** in `theme.yml`. The same foundation looks different across sites because tokens resolve from the site's color configuration, not from component code.
434
487
 
435
488
  ### theme.yml
@@ -437,18 +490,14 @@ theme: dark ← sets context-dark, all tokens resolve to dark values
437
490
  ```yaml
438
491
  # site/theme.yml
439
492
  colors:
440
- primary:
441
- base: '#3b82f6'
442
- exactMatch: true # Use this exact hex at the 500 shade
493
+ primary: '#3b82f6' # Your exact hex appears at shade 500
443
494
  secondary: '#64748b'
444
495
  accent: '#8b5cf6'
445
- neutral: stone # Named preset: stone, zinc, gray, slate, neutral
496
+ neutral: stone # Named preset: stone, zinc, gray, slate, neutral
446
497
 
447
498
  contexts:
448
499
  light:
449
- section: '#fafaf9' # Override individual tokens per context
450
- primary: var(--primary-500)
451
- primary-hover: var(--primary-600)
500
+ section: '#fafaf9' # Override individual tokens per context
452
501
 
453
502
  fonts:
454
503
  import:
@@ -467,23 +516,78 @@ vars: # Override foundation-declared variables
467
516
 
468
517
  Each color generates 11 OKLCH shades (50–950). The `neutral` palette is special — use a named preset (`stone` for warm) rather than a hex value. Three contexts are built-in: `light` (default), `medium`, `dark`. Context override keys match token names — `section:` not `bg:`, `primary:` not `btn-primary-bg:`.
469
518
 
519
+ ### How colors reach components
520
+
521
+ Your hex color → 11 shades (50–950) → semantic tokens → components.
522
+
523
+ **Shade 500 = your exact input color.** The build generates lighter shades (50–400) above it and darker shades (600–950) below it, redistributing lightness proportionally to maintain a smooth scale. Set `exactMatch: false` on a color to opt out and use fixed lightness values instead.
524
+
525
+ Semantic tokens map shades to roles. The defaults for light/medium contexts:
526
+
527
+ | Token | Shade | Purpose |
528
+ |-------|-------|---------|
529
+ | `--primary` | 600 | Button background |
530
+ | `--primary-hover` | 700 | Button hover |
531
+ | `--link` | 600 | Link color |
532
+ | `--ring` | 500 | Focus ring |
533
+
534
+ In dark contexts, `--primary` uses shade 500 and `--link` uses shade 400.
535
+
536
+ **Buttons and links use shade 600 — darker than your input.** This is an accessibility choice: shade 600 provides better contrast with white button text. For medium-bright brand colors like orange, buttons will be noticeably darker than the brand color.
537
+
538
+ **Recipe — brand-exact buttons:**
539
+
540
+ ```yaml
541
+ colors:
542
+ primary: "#E35D25"
543
+
544
+ contexts:
545
+ light:
546
+ primary: primary-500 # Your exact color on buttons
547
+ primary-hover: primary-600 # Darker on hover
548
+ ```
549
+
550
+ > **Contrast warning:** Bright brand colors (orange, yellow, light green) at shade 500 may not meet WCAG contrast (4.5:1) with white foreground text. Test buttons for readability — if contrast is insufficient, keep the default shade 600 mapping or darken your base color.
551
+
470
552
  ### Foundation variables
471
553
 
472
- Foundations declare customizable layout/spacing values in `foundation.js`:
554
+ Foundations declare customizable layout/spacing values in `foundation.js`. The starter includes:
473
555
 
474
556
  ```js
475
- export default {
476
- vars: {
477
- 'header-height': { default: '4rem' },
478
- 'sidebar-width': { default: '280px' },
479
- },
557
+ export const vars = {
558
+ 'header-height': { default: '4rem', description: 'Fixed header height' },
559
+ 'max-content-width': { default: '80rem', description: 'Maximum content width' },
560
+ 'section-padding-y': { default: 'clamp(4rem, 6vw, 7rem)', description: 'Vertical padding for sections' },
480
561
  }
481
562
  ```
482
563
 
483
- Sites override them in `theme.yml` under `vars:`. Components use them as `var(--header-height)`.
564
+ Sites override them in `theme.yml` under `vars:`. Components use them via Tailwind arbitrary values or CSS: `py-[var(--section-padding-y)]`, `h-[var(--header-height)]`, etc.
565
+
566
+ The `section-padding-y` default uses `clamp()` for fluid spacing — tighter on mobile, more breathing room on large screens. Use this variable for consistent section spacing instead of hardcoding padding in each component. Sites can override to a fixed value (`section-padding-y: 3rem`) or a different clamp in `theme.yml`.
484
567
 
485
568
  **When to break the rules:** Header/footer components that float over content may need direct color logic (reading the first section's theme). Decorative elements with fixed branding (logos) use literal colors.
486
569
 
570
+ ### Design richness beyond tokens
571
+
572
+ Semantic tokens handle context adaptation — the hard problem of making colors work in light, medium, and dark sections. **They are a floor, not a ceiling.** A great foundation adds its own design vocabulary on top.
573
+
574
+ The token set is deliberately small (24 tokens). It covers the dimensions that change per context. Everything that stays constant across contexts — border weights, shadow depth, radius scales, gradient angles, accent borders, glassmorphism, elevation layers — belongs in your foundation's `styles.css` or component code.
575
+
576
+ **Don't flatten a rich design to fit the token set.** If a source design has 4 border tones, create them:
577
+
578
+ ```css
579
+ /* foundation/src/styles.css */
580
+ .border-subtle { border-color: color-mix(in oklch, var(--border), transparent 50%); }
581
+ .border-strong { border-color: color-mix(in oklch, var(--border), var(--heading) 30%); }
582
+ .border-accent { border-color: var(--primary-300); }
583
+ ```
584
+
585
+ These compose with semantic tokens — they adapt per context because they reference `--border`, `--heading`, or palette shades. But they add design nuance the token set alone doesn't provide.
586
+
587
+ **The priority:** Design quality > portability > configurability. It's better to ship a foundation with beautiful, detailed design that's less configurable than to ship a generic one that looks flat. A foundation that looks great for one site is more valuable than one that looks mediocre for any site.
588
+
589
+ **When migrating from an existing design**, map every visual detail — not just the ones that have a semantic token. Shadow systems, border hierarchies, custom hover effects, accent tints: create CSS classes or Tailwind utilities in `styles.css` for anything the original has that tokens don't cover. Use palette shades directly (`var(--primary-300)`, `bg-neutral-200`) for fine-grained color control beyond the semantic tokens.
590
+
487
591
  ## Component Development
488
592
 
489
593
  ### Props Interface
@@ -509,13 +613,13 @@ function Hero({ content, params }) {
509
613
  )
510
614
  }
511
615
 
512
- Hero.className = 'pt-32 md:pt-48' // Classes on the <section> wrapper
616
+ Hero.className = 'pt-32 md:pt-48' // Override spacing for hero (more top padding)
513
617
  Hero.as = 'div' // Change wrapper element (default: 'section')
514
618
 
515
619
  export default Hero
516
620
  ```
517
621
 
518
- - `Component.className` — adds classes to the runtime's wrapper. Use for section-level padding, borders, overflow. The component's own JSX handles inner layout only (`max-w-7xl mx-auto px-6`).
622
+ - `Component.className` — adds classes to the runtime's wrapper. Use for section-level spacing, borders, overflow. Set `py-[var(--section-padding-y)]` for consistent spacing from the theme variable, or override for specific sections (e.g., hero needs extra top padding). The component's own JSX handles inner layout only (`max-w-7xl mx-auto px-6`).
519
623
  - `Component.as` — changes the wrapper element. Use `'nav'` for headers, `'footer'` for footers, `'div'` when `<section>` isn't semantically appropriate.
520
624
 
521
625
  ### meta.js Structure
@@ -577,6 +681,8 @@ import { H2, P, Span } from '@uniweb/kit'
577
681
  <Text text={content.title} as="h2" className="..." /> // explicit tag
578
682
  ```
579
683
 
684
+ These components render their own HTML tag — don't wrap them in a matching tag. `<h2><H2 text={...} /></h2>` creates a nested `<h2><h2>...</h2></h2>`, which is invalid HTML. Just use `<H2 text={...} />` directly.
685
+
580
686
  Don't render content strings with `{content.paragraphs[0]}` in JSX — that shows HTML tags as visible text. Use `<P>`, `<H2>`, `<Span>`, etc. for content strings.
581
687
 
582
688
  **Rendering full content** (`@uniweb/kit`):
@@ -853,7 +959,7 @@ Foundation styles in `foundation/src/styles.css`:
853
959
 
854
960
  Semantic color tokens (`text-heading`, `bg-section`, `bg-primary`, etc.) come from `theme-tokens.css` — which the runtime populates from the site's `theme.yml`. Don't redefine colors here that belong in `theme.yml`. Use `@theme` only for values the token system doesn't cover (custom breakpoints, animations, shadows).
855
961
 
856
- **Custom CSS is fine alongside Tailwind.** Animations, keyframes, gradients with masks, always-dark code blocks, and other effects that aren't expressible as utility classes can go directly in `styles.css`. Tailwind handles layout and spacing; custom CSS handles visual effects.
962
+ **Custom CSS is expected alongside Tailwind.** Your foundation's `styles.css` is the design layer — shadow systems, border hierarchies, gradient effects, accent treatments, elevation scales, glassmorphism. If the source design has a visual detail, create a class for it. Tailwind handles layout and spacing; semantic tokens handle context adaptation; `styles.css` handles everything else that makes the design rich and distinctive.
857
963
 
858
964
  ## Troubleshooting
859
965
 
@@ -863,6 +969,8 @@ Semantic color tokens (`text-heading`, `bg-section`, `bg-primary`, etc.) come fr
863
969
 
864
970
  **Styles not applying** — Verify `@source` in `styles.css` includes your component paths. Check custom colors match `@theme` definitions.
865
971
 
972
+ **Prerender warnings about hooks/useState** — During `pnpm build`, you may see `Warning: Failed to render /: Cannot read properties of null (reading 'useState')` for pages. This is a known limitation of the SSG pipeline (dual React instances in development). The site works correctly client-side — these warnings only affect the static HTML preview, not functionality.
973
+
866
974
  ## Further Documentation
867
975
 
868
976
  Full Uniweb documentation is available at **https://github.com/uniweb/docs** — raw markdown files you can fetch directly.
@@ -24,8 +24,8 @@ export const vars = {
24
24
  description: 'Maximum content width (1280px)',
25
25
  },
26
26
  'section-padding-y': {
27
- default: '5rem',
28
- description: 'Vertical padding for sections',
27
+ default: 'clamp(4rem, 6vw, 7rem)',
28
+ description: 'Vertical padding for sections (fluid: adapts to viewport)',
29
29
  },
30
30
  }
31
31