uniweb 0.8.9 → 0.8.10

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 (2) hide show
  1. package/package.json +2 -2
  2. package/partials/agents.md +118 -43
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.8.9",
3
+ "version": "0.8.10",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "js-yaml": "^4.1.0",
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
- "@uniweb/build": "0.8.8",
44
+ "@uniweb/build": "0.8.9",
45
45
  "@uniweb/content-reader": "1.1.4",
46
46
  "@uniweb/kit": "0.7.6",
47
47
  "@uniweb/core": "0.5.6",
@@ -50,10 +50,10 @@ This project was created with [Uniweb CLI](https://github.com/uniweb/cli). Full
50
50
  | Component metadata (meta.js) | `reference/component-metadata.md` |
51
51
  | Migrating existing designs | `development/converting-existing.md` |
52
52
 
53
- > **npm registry:** Use `https://registry.npmjs.org/uniweb` for package metadata — the npmjs.com website blocks automated requests.
54
-
55
53
  ## Project Structure
56
54
 
55
+ Most projects start as a workspace with two packages:
56
+
57
57
  ```
58
58
  project/
59
59
  ├── foundation/ # Component developer's domain
@@ -61,23 +61,24 @@ project/
61
61
  └── pnpm-workspace.yaml
62
62
  ```
63
63
 
64
- Multi-site variant uses `foundations/` and `sites/` (plural) folders.
64
+ - **Foundation** (developer): React components. Those in `src/sections` and `src/layouts` are *section types* — selectable by content authors via `type:` in frontmatter, or used for site-level layout areas (header, footer, panel). Most have an a `meta.js` with metadata in them. Everything in `src/components` (or elsewhere) is ordinary React.
65
+ - **Site** (content author): Markdown content + configuration. Each section file references a section type. Authors work here without touching foundation code. It may also contain collections of structured content and/or references to external data sources.
65
66
 
66
- - **Foundation** (developer): React components. Those with `meta.js` are *section types* selectable by content authors via `type:` in frontmatter. Everything else is ordinary React.
67
- - **Site** (content author): Markdown content + configuration. Each section file references a section type. Authors work here without touching foundation code.
67
+ > Multi-site projects use sub-folders with site/foundation pairs in them, or segregate foundations and sites into separate folders (`foundations/`, `sites/`).
68
68
 
69
69
  ## Project Setup
70
70
 
71
71
  Always use the CLI to scaffold projects — never write `package.json`, `vite.config.js`, `main.js`, or `index.html` manually. The CLI resolves correct versions and structure.
72
72
 
73
+ **npm or pnpm.** Projects include both `pnpm-workspace.yaml` and npm workspaces. Replace `pnpm` with `npm` in any command below.
74
+
73
75
  ### New workspace
74
76
 
75
77
  ```bash
76
- pnpm create uniweb my-project
78
+ pnpm create uniweb my-project --template <n>
77
79
  cd my-project && pnpm install
78
80
  ```
79
-
80
- This creates a workspace with foundation + site + starter content — two commands to a dev server. Use `--template <n>` for an official template (`marketing`, `docs`, `academic`, etc.), `--template none` for foundation + site with no content, or `--blank` for an empty workspace.
81
+ Use `--template <n>` for an official template (`none`, `starter`, `marketing`, `docs`, `academic`, etc.), `--template none` for foundation + site with no content, or `--blank` for an empty workspace.
81
82
 
82
83
  ### Adding a co-located project
83
84
 
@@ -129,8 +130,6 @@ pnpm build # Build for production
129
130
  pnpm preview # Preview production build (SSG + SPA)
130
131
  ```
131
132
 
132
- > **npm works too.** Projects include both `pnpm-workspace.yaml` and npm workspaces. Replace `pnpm` with `npm` in any command above.
133
-
134
133
  ---
135
134
 
136
135
  ## Content Authoring
@@ -360,7 +359,9 @@ submitLabel: Send
360
359
 
361
360
  Access: `content.data?.form` → `{ fields: [...], submitLabel: "Send" }`
362
361
 
363
- **Untagged code blocks** (plain ``` js) are only visible to sequential-rendering components. If a component needs to access code blocks by name, tag them (`jsx:before`, `jsx:after` `content.data?.before`, `content.data?.after`).
362
+ > **FIXME:** The claim below is wrong. I think tagged blocks an only be YAML/JSON. We could add regular code blocks to the parsed content as an array of code blocks or "pre" elements.
363
+
364
+ **Untagged code blocks** are only visible to sequential-rendering components. If a component needs to access code blocks by name, tag them (`jsx:before`, `jsx:after` → `content.data?.before`, `content.data?.after`).
364
365
 
365
366
  ### Composition: Nesting and Embedding
366
367
 
@@ -461,6 +462,8 @@ export default function Grid({ block, params }) {
461
462
 
462
463
  Set `background` in frontmatter — the runtime renders it automatically:
463
464
 
465
+ > **FIXME:** var(--primary-900) should be primary-900, right?
466
+
464
467
  ```yaml
465
468
  background: /images/hero.jpg # Image
466
469
  background: /videos/hero.mp4 # Video
@@ -916,8 +919,20 @@ export default {
916
919
  compact: { label: 'Compact', params: { columns: 4 } },
917
920
  },
918
921
 
922
+ // context and initialState: keys are developer-defined, not framework fields.
923
+ // Design your own names for your foundation's cross-block communication.
924
+
925
+ // Static — neighbors read via getNextBlockInfo().context
919
926
  context: {
920
- allowTranslucentTop: true,
927
+ // Example: a Hero might declare this so a Header knows it can float.
928
+ // allowTranslucentTop: true,
929
+ },
930
+
931
+ // Dynamic — neighbors read via getNextBlockInfo().state
932
+ // Component can update with useBlockState()
933
+ initialState: {
934
+ // Example: Hero starts translucent-ready, but component logic may disable it.
935
+ // allowTranslucentTop: true,
921
936
  },
922
937
  }
923
938
  ```
@@ -928,46 +943,52 @@ All defaults belong in `meta.js`, not inline in component code.
928
943
 
929
944
  Section types naturally use params to adjust their own rendering — `variant: flipped` reverses a flex direction, `columns: 3` sets a grid. That's not a pattern, that's the baseline.
930
945
 
931
- The **Front Desk pattern** is when a section type does virtually no rendering itself. It reads the author's params, picks the right helper component from `src/components/`, and translates author-friendly vocabulary into developer-oriented props. The section type is a front desk — it greets the request and routes it to the right specialist:
946
+ The **Front Desk pattern** is when a section type does virtually no rendering itself. It reads the author's params, picks the right helper component, and translates author-friendly vocabulary into developer-oriented props. The section type is a front desk — it greets the request and routes it to the right specialist.
947
+
948
+ The workers behind the front desk don't need to share the same interface. A `Hero` might delegate to a `SliderHero` that renders an image carousel and a `ContactHero` that renders a quote request form. They expect different content and different props — that's fine. The front desk declares the **union** of all content its workers might need. Some content won't be used for a given variant, and that's perfectly normal in CCA — params change behavior, and that includes not rendering some content:
949
+
950
+ ```js
951
+ // meta.js — the union of all variants' needs
952
+ export default {
953
+ params: {
954
+ variant: { type: 'select', options: ['slider', 'contact'], default: 'slider' },
955
+ slideInterval: { type: 'number', default: 5 },
956
+ density: { type: 'select', options: ['default', 'compact'], default: 'default' },
957
+ style: { type: 'select', options: ['default', 'dramatic'], default: 'default' },
958
+ }
959
+ }
960
+ ```
932
961
 
933
962
  ```jsx
934
- // sections/Features/index.jsx — the front desk
935
- import { CardGrid } from '../../components/CardGrid'
936
- import { CardList } from '../../components/CardList'
937
- import { ComparisonTable } from '../../components/ComparisonTable'
963
+ // sections/Hero/index.jsx — the front desk
964
+ import { SliderHero } from '../../components/SliderHero'
965
+ import { ContactHero } from '../../components/ContactHero'
938
966
 
939
- const variants = { grid: CardGrid, list: CardList, comparison: ComparisonTable }
967
+ const variants = { slider: SliderHero, contact: ContactHero }
940
968
 
941
- export default function Features({ content, block, params }) {
942
- const Layout = variants[params.variant] || CardGrid
969
+ export default function Hero({ content, block, params }) {
970
+ const Variant = variants[params.variant] || SliderHero
943
971
 
944
972
  return (
945
- <Layout
973
+ <Variant
974
+ // Shared — every variant gets these
946
975
  title={content.title}
947
976
  subtitle={content.paragraphs[0]}
948
- items={content.items}
977
+ links={content.links}
949
978
  block={block}
950
- columns={params.columns}
951
- showIcons={params.showIcon !== false}
979
+ // Content that only some variants use
980
+ images={content.imgs}
981
+ formData={content.data?.quote}
982
+ // Translated params — author vocabulary → developer props
983
+ interval={params.slideInterval}
952
984
  compact={params.density === 'compact'}
985
+ transition={params.style === 'dramatic' ? 'zoom' : 'fade'}
953
986
  />
954
987
  )
955
988
  }
956
989
  ```
957
990
 
958
- ```js
959
- // meta.js — author-friendly language
960
- export default {
961
- params: {
962
- variant: { type: 'select', options: ['grid', 'list', 'comparison'], default: 'grid' },
963
- columns: { type: 'number', default: 3 },
964
- showIcon: { type: 'boolean', default: true },
965
- density: { type: 'select', options: ['default', 'compact'], default: 'default' },
966
- }
967
- }
968
- ```
969
-
970
- The content author writes `variant: comparison` — they don't know or care about `ComparisonTable`. The section type translates `density: compact` into a `compact={true}` prop. `CardGrid`, `CardList`, `ComparisonTable` live in `src/components/` — ordinary React, reusable across multiple section types, testable independently.
991
+ `SliderHero` uses `images`, `interval`, and `transition`; it ignores `formData` and `compact`. `ContactHero` uses `formData` and `compact`; it ignores `images` and `interval`. Each worker takes what it needs. Some params only matter for certain variants (`slideInterval` for slider, `density` for contact). Some are high-level names that the front desk translates into developer-oriented values (`style: dramatic` → `transition="zoom"`). The content author writes `variant: contact` — they don't know or care about `ContactHero`.
971
992
 
972
993
  This is the system-building pattern at its clearest: **section types are the public interface** to your content system (author-friendly names, documented in `meta.js`). **Helper components are the implementation** (developer-friendly APIs, ordinary React props). The section type is the thin translation layer that connects the two worlds.
973
994
 
@@ -1027,11 +1048,65 @@ page.hasChildren(), page.children
1027
1048
 
1028
1049
  ### Cross-Block Communication
1029
1050
 
1051
+ Section types sometimes need to coordinate. The typical case: a Header needs to know whether the section below it supports a floating translucent overlay — a Hero with a full-bleed background does, a plain text section doesn't. The section that **owns the capability declares it**; the section that **needs to adapt reads it**.
1052
+
1053
+ `getBlockInfo()` exposes two channels:
1054
+
1055
+ - **`context`** — Static capabilities from `meta.js`. Never changes. The declaring section type always has this capability.
1056
+ - **`state`** — Dynamic runtime state via `useBlockState()`. Can change based on component logic. Initial value comes from `initialState` in `meta.js`.
1057
+
1058
+ ```jsx
1059
+ // Header reads the next section's info to decide how to render
1060
+ const nextBlockInfo = block.getNextBlockInfo()
1061
+ // nextBlockInfo.context → static (meta.js)
1062
+ // nextBlockInfo.state → dynamic (useBlockState)
1063
+ ```
1064
+
1065
+ **Static context** — Hero declares a permanent capability, Header reads it:
1066
+
1067
+ ```js
1068
+ // Hero/meta.js — "I always support a translucent header over me"
1069
+ export default {
1070
+ context: { allowTranslucentTop: true },
1071
+ }
1072
+ ```
1073
+
1074
+ ```jsx
1075
+ // Header/index.jsx — adapts based on what's below
1076
+ const nextBlockInfo = block.getNextBlockInfo()
1077
+ const isFloating = nextBlockInfo?.context?.allowTranslucentTop || false
1078
+ ```
1079
+
1080
+ **Dynamic state** — Hero declares an initial value but can change it at runtime:
1081
+
1082
+ ```js
1083
+ // Hero/meta.js — starts as true, but component logic may change it
1084
+ export default {
1085
+ initialState: { allowTranslucentTop: true },
1086
+ }
1087
+ ```
1088
+
1030
1089
  ```jsx
1031
- const firstBody = block.page.getFirstBodyBlockInfo()
1032
- // { type, theme, context: { allowTranslucentTop }, state }
1090
+ // Hero/index.jsx conditionally updates
1091
+ function Hero({ content, block }) {
1092
+ const [state, setState] = block.useBlockState(useState)
1093
+ // state.allowTranslucentTop is true initially (from meta.js)
1094
+ // Component logic can change it: setState({ allowTranslucentTop: false })
1095
+ }
1033
1096
  ```
1034
1097
 
1098
+ ```jsx
1099
+ // Header/index.jsx — reads dynamic state, falls back to static context
1100
+ const nextBlockInfo = block.getNextBlockInfo()
1101
+ const isFloating = nextBlockInfo?.state?.allowTranslucentTop
1102
+ ?? nextBlockInfo?.context?.allowTranslucentTop
1103
+ ?? false
1104
+ ```
1105
+
1106
+ The key names (`allowTranslucentTop`, `expanded`, etc.) are yours to design — they're not framework fields. Define whatever protocol your foundation's sections need.
1107
+
1108
+ Other navigation methods: `block.getPrevBlockInfo()`, `block.page.getFirstBodyBlockInfo()`.
1109
+
1035
1110
  ### Custom Layouts
1036
1111
 
1037
1112
  Layouts live in `foundation/src/layouts/` and are auto-discovered:
@@ -1154,9 +1229,9 @@ Semantic tokens come from `theme-tokens.css` (populated from `theme.yml`). Use `
1154
1229
 
1155
1230
  **Content not appearing as expected?**
1156
1231
  ```bash
1157
- uniweb inspect pages/home/hero.md # Single section
1158
- uniweb inspect pages/home/ # Whole page
1159
- uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1232
+ pnpm uniweb inspect pages/home/hero.md # Single section
1233
+ pnpm uniweb inspect pages/home/ # Whole page
1234
+ pnpm uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1160
1235
  ```
1161
1236
 
1162
1237
  ## Learning from Official Templates
@@ -1164,7 +1239,7 @@ uniweb inspect pages/home/hero.md --raw # ProseMirror AST
1164
1239
  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:
1165
1240
 
1166
1241
  ```bash
1167
- uniweb add project marketing --from marketing
1242
+ pnpm uniweb add project marketing --from marketing
1168
1243
  pnpm install
1169
1244
  ```
1170
1245