uniweb 0.51.2 → 0.52.0
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 +7 -7
- package/partials/agents.md +29 -10
- package/src/framework-index.json +9 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.0",
|
|
4
4
|
"description": "Create structured Vite + React sites with content/code separation",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -41,15 +41,15 @@
|
|
|
41
41
|
"js-yaml": "^4.1.0",
|
|
42
42
|
"prompts": "^2.4.2",
|
|
43
43
|
"tar": "^7.0.0",
|
|
44
|
-
"@uniweb/
|
|
45
|
-
"@uniweb/
|
|
46
|
-
"@uniweb/core": "^0.
|
|
47
|
-
"@uniweb/
|
|
44
|
+
"@uniweb/kit": "^0.18.5",
|
|
45
|
+
"@uniweb/semantic-parser": "^1.4.0",
|
|
46
|
+
"@uniweb/core": "^0.27.0",
|
|
47
|
+
"@uniweb/runtime": "^0.23.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@uniweb/semantic-parser": "^1.4.0",
|
|
51
50
|
"@uniweb/content-reader": "^1.2.4",
|
|
52
|
-
"@uniweb/
|
|
51
|
+
"@uniweb/semantic-parser": "^1.4.0",
|
|
52
|
+
"@uniweb/build": "^0.48.0"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"@uniweb/build": {
|
package/partials/agents.md
CHANGED
|
@@ -876,6 +876,12 @@ You can keep the same declarations under `queries:` in `site.yml` instead, if yo
|
|
|
876
876
|
|
|
877
877
|
**Show a query on a page** with `query:` in `page.yml` or a section's frontmatter (the whole result), or `fetch:` for anything more — a `limit`, a `where`. A list — `query: [team, articles]` — declares several, each arriving under its own `content.data` key. `query:` takes names only; `data:`, its old name, is now an error.
|
|
878
878
|
|
|
879
|
+
**Who receives it:** a section's own declaration reaches that section; a page's, every section on the page; a parent page's, the sections of the pages directly under it. **The site is the root page:** `query:` in `site.yml` reaches the layout areas (header, footer, …) and the sections of top-level pages — the homepage included — and no page further down. A section on `/docs/setup` that needs site-wide data names the query itself.
|
|
880
|
+
|
|
881
|
+
**A fetch always names a query.** A string in `fetch:` is a query name: `fetch: team` is `fetch: { query: team }`, and `fetch: [team, articles]` is a list of those. A fetch never names a file — `/data/<query>.json` is what the build generates from a query for a site with no backend, and writing that path in a `fetch:` stops the build. That is what keeps a site portable: name the query, debug locally against the generated data, publish, and the same page reads live records from the host with nothing changed.
|
|
882
|
+
|
|
883
|
+
**A `fetch:` narrows its query, never widens it:** its `where` must hold beside the query's, and its `sort` and `limit` replace the query's (a larger `limit` is fine). That is the whole list — `scope` belongs to the query, and the build stops on a `fetch:` that carries one; for another folder branch, declare another query. A `limit` is how many a list shows, never which records exist: every record the query selects compiles and gets its detail page. Two entries under one key at one level: the first is used, and the build warns.
|
|
884
|
+
|
|
879
885
|
```yaml
|
|
880
886
|
# pages/blog/page.yml | # a section on the homepage
|
|
881
887
|
title: Blog | ---
|
|
@@ -897,7 +903,7 @@ pages/blog/
|
|
|
897
903
|
|
|
898
904
|
`entities/article/design-tips.md` becomes `/blog/design-tips`. The section inside `[slug]/` needs no special markdown — the matched record is delivered to it. Generated pages are excluded from navigation menus.
|
|
899
905
|
|
|
900
|
-
**Which query the URL narrows — the page's route query:** the `[slug]` page's own `query:`, else its parent page's (the usual shape, above), else `site.yml`'s; if none declares one, the query its sections all declare. The first query of that level wins. Every section the route query reaches gets the one record; a section declaring a *different* query of its own gets that query as declared. The folder name says what the URL segment matches: `[slug]` the record's handle (`$name`, which compiled records carry — equal to their `slug`), `[uuid]` its `$uuid`, any other `[name]` the record's own field of that name — and when that field holds several values, **any member** matches (the record's own link is its first value). Routing by a field that is not unique picks one record and which one is not guaranteed; the build warns. A folder inside `[slug]/` (`[slug]/cv/` → `/blog/:slug/cv`) is a parametric page too,
|
|
906
|
+
**Which query the URL narrows — the page's route query:** the `[slug]` page's own `query:`, else its parent page's (the usual shape, above), else `site.yml`'s (for a top-level `pages/[slug]/` only — the site's reaches no deeper page); if none declares one, the query its sections all declare. The first query of that level wins. Every section the route query reaches gets the one record; a section declaring a *different* query of its own gets that query as declared. The folder name says what the URL segment matches: `[slug]` the record's handle (`$name`, which compiled records carry — equal to their `slug`), `[uuid]` its `$uuid`, any other `[name]` the record's own field of that name — and when that field holds several values, **any member** matches (the record's own link is its first value). Routing by a field that is not unique picks one record and which one is not guaranteed; the build warns. A folder inside `[slug]/` (`[slug]/cv/` → `/blog/:slug/cv`) is a parametric page too, about the same record: its route query is the `[slug]` page's, wherever that is declared, and a query `cv/` declares itself arrives under its own key. **A section chooses how it uses the page's record with `current:`** on its own `fetch:` under the route query's key — `only` (the default: the record, as a list of one), `exclude` (the others: "related"), `include` (all of them: a pager) — e.g. `fetch: { query: recent, current: exclude, limit: 3 }`, where `limit` counts the others. `refine: true` / `detail: false` are retired and stop the build. `[dir]` and `[path]` are refused as folder names, and so is any folder inside `[...path]/`.
|
|
901
907
|
|
|
902
908
|
> **The record arrives as a single-element array under the query key** — `content.data.recent[0]`, not `content.data.article`. The runtime never coerces it to an object and never synthesizes a singular key. See *Data* in Part 4.
|
|
903
909
|
|
|
@@ -905,7 +911,7 @@ pages/blog/
|
|
|
905
911
|
|
|
906
912
|
**Two options for bigger sets:**
|
|
907
913
|
|
|
908
|
-
`deferred: [body]` strips heavy fields from the list payload — cards stay light, while a `[slug]` page still receives the full record automatically and other components fetch on demand via `useEntityDetail`.
|
|
914
|
+
`deferred: [body]` strips heavy fields from the list payload — cards stay light, while a `[slug]` page still receives the full record automatically and other components fetch on demand via `useEntityDetail`. File-based records emit per-record files at `/data/<name>/<slug>.json` and need no configuration; an external query names its one-record request with `record:` instead (*Fetching from other sources* in Part 4).
|
|
909
915
|
|
|
910
916
|
`queryable:` declares which fields a reader may filter on, with enough metadata for the foundation to render controls:
|
|
911
917
|
|
|
@@ -1608,7 +1614,7 @@ export default function Grid({ block, params }) {
|
|
|
1608
1614
|
|
|
1609
1615
|
Each child is a regular section with its own type, params, and content — and you're in the middle: wrap each child, filter by type, reorder, add container classes. The author decides *what* goes in the grid; your component decides *how* it renders. Tomorrow the author can swap a child for a different section type with no code change, and your components stay reusable wherever child sections are accepted.
|
|
1610
1616
|
|
|
1611
|
-
**Data and child blocks:** page-level `query:` (or `fetch:`) is available to all blocks including children, and each child resolves data independently through the page → site hierarchy. If a child needs data no ancestor declares, give it its own in its frontmatter (`query: articles`, or `fetch:`). Its `meta.js` `data:` declares the shape it reads, never where the data comes from — it fetches nothing.
|
|
1617
|
+
**Data and child blocks:** page-level `query:` (or `fetch:`) is available to all blocks including children, and each child resolves data independently through the page → parent page → site hierarchy (the site's only on a top-level page). If a child needs data no ancestor declares, give it its own in its frontmatter (`query: articles`, or `fetch:`). Its `meta.js` `data:` declares the shape it reads, never where the data comes from — it fetches nothing.
|
|
1612
1618
|
|
|
1613
1619
|
**SSG:** insets, `<ChildBlocks>`, and `<Visual>` all render correctly during prerender. Inset components using React hooks internally trigger prerender warnings — expected and harmless; the page renders correctly client-side.
|
|
1614
1620
|
|
|
@@ -1667,7 +1673,7 @@ Layouts are full components with their own `params` in `meta.js`, not just struc
|
|
|
1667
1673
|
|
|
1668
1674
|
Two optional keys tune how areas behave across a navigation. `transitions` renames or opts regions out of per-area view transitions (`{ left: null }`, or `false` for the whole layout). `layers` sets which area paints on top — every area is stacked above the body by default, so a fixed header works without declaring anything, but areas are equal to each other, so a layout whose chrome overlaps says which wins: `layers: { header: 2, left: 1 }`. Both take an object to override per region, or `false` to opt out. **A `z-index` inside an area cannot lift it past another area** — each area is its own stacking context — so reach for `layers` rather than a bigger number, and for `<Overlay>` when a modal needs to escape the area entirely.
|
|
1669
1675
|
|
|
1670
|
-
**Layout content** lives in `site/layout/` — `header.md`, `footer.md` for the default layout, or a named subdirectory (`site/layout/marketing/`) for named layouts. Named subdirectories are self-contained — no inheritance. Cascade: `page.yml` → `folder.yml` → `site.yml` → foundation `defaultLayout` → `"default"`.
|
|
1676
|
+
**Layout content** lives in `site/layout/` — `header.md`, `footer.md` for the default layout, or a named subdirectory (`site/layout/marketing/`) for named layouts. Named subdirectories are self-contained — no inheritance. Cascade: `page.yml` → `folder.yml` → `site.yml` → foundation `defaultLayout` → `"default"`. The structure alone decides what an entry is: every folder directly under `layout/` is a named layout (its name matched regardless of case and of a trailing `Layout` — `site/layout/docs/` serves the foundation's `DocsLayout`), and an area with several sections is a folder inside a layout's folder — `site/layout/default/header/1-topbar.md` for the default layout. An area has no `page.yml`; its sections render in filename order.
|
|
1671
1677
|
|
|
1672
1678
|
Layout sections are regular section types — they support the full content shape, including tagged data blocks, lists, links, and items. The only difference is they render on every page. Each content category takes a different role:
|
|
1673
1679
|
|
|
@@ -1815,17 +1821,30 @@ fetch:
|
|
|
1815
1821
|
limit: 3
|
|
1816
1822
|
```
|
|
1817
1823
|
|
|
1818
|
-
**Lean lists with `deferred:`.** A query over records with heavy fields (article bodies, large nested arrays) can declare `deferred: [body]`. The cascade payload omits those fields; per-record full files are emitted at `/data/<name>/<slug>.json
|
|
1824
|
+
**Lean lists with `deferred:`.** A query over records with heavy fields (article bodies, large nested arrays) can declare `deferred: [body]`. The cascade payload omits those fields; per-record full files are emitted at `/data/<name>/<slug>.json`. (An external query declares no `deferred:` — its whole record comes from `record:`, below.) On dynamic-route pages the focused record's full data is delivered automatically; elsewhere components fetch on demand via `useEntityDetail`. The hook is safe to call on any query: when there is no separate detail source it returns the record you passed in, because nothing was stripped from it.
|
|
1819
1825
|
|
|
1820
1826
|
**Component-side fetching.** When a component genuinely needs to fetch on its own (a search box, "load more", a lazy popover), use the kit hooks — `useFetched`, `useCacheEntry`, `useEntityDetail`. They share the framework's cache and dispatcher with declarative fetches; same-key requests dedupe automatically.
|
|
1821
1827
|
|
|
1822
|
-
**Validate before shipping.** `uniweb validate` checks file-based data against your declared schemas — missing required fields, type/enum/format mismatches, nested fields. Warns by default; `--strict` for a non-zero CI exit. Distinct from `uniweb doctor` (project structure): `validate` checks your *data* against the schemas you *declared*.
|
|
1828
|
+
**Validate before shipping.** `uniweb validate` checks file-based data against your declared schemas — missing required fields, type/enum/format mismatches, nested fields. Warns by default; `--strict` for a non-zero CI exit. Distinct from `uniweb doctor` (project structure): `validate` checks your *data* against the schemas you *declared*. External queries (`url:`), `ref`/`options`, and rich `sections`-form inputs are reported deferred.
|
|
1823
1829
|
|
|
1824
1830
|
### Fetching from other sources (`fetcher:`)
|
|
1825
1831
|
|
|
1826
|
-
A site isn't limited to
|
|
1832
|
+
A site isn't limited to its own records. A public, keyless JSON API is an **external query** — a query with `url:`, declared beside the others and named by pages the same way (`query: posts`):
|
|
1833
|
+
|
|
1834
|
+
```yaml
|
|
1835
|
+
# queries.yml
|
|
1836
|
+
posts:
|
|
1837
|
+
url: https://api.example.com/posts
|
|
1838
|
+
transform: data.items # dot-path to the records in the response
|
|
1839
|
+
record: # one whole post, for a [id] page
|
|
1840
|
+
url: https://api.example.com/posts/{id}
|
|
1841
|
+
```
|
|
1842
|
+
|
|
1843
|
+
It also takes `method: POST` with a `body`, `where` / `sort` / `limit` (evaluated over what arrived, after `transform`) and `queryable`. `record:` takes `url`, `method`, `body` and `transform`: `url` and `method` default to the query's, `body` and `transform` never carry over, and a placeholder named by the page's folder (`{id}` for `[id]`, `{slug}` for `[slug]`) is its URL segment. `schema`, `scope`, `deferred`, `excerpt`, `route` and `path` are refused beside `url:` — they describe the site's own records. The visitor's browser fetches an external query (a `fetch:` with `prerender: true` makes the build fetch it instead); it is never asked of a host's records service and compiles no `/data` file. On a `[id]` page the list still decides which records exist — `record:` fetches the one it found, whole. A site published to a Uniweb host reads the host's records with no configuration at all.
|
|
1844
|
+
|
|
1845
|
+
⛔ A `fetch:` never carries a source: `path`, `url`, `method`, `body`, `transform`, `detail` and `scope` on a binding stop the build. The `detail:` forms (`rest`, `query`, a URL pattern) are now an external query's `record:`, and `detailUrl:` is `record.url`.
|
|
1827
1846
|
|
|
1828
|
-
A backend
|
|
1847
|
+
A backend that needs a key, headers, paging, its own wire or query language is a **transport**: a named `{ resolve, cacheKey? }` exported by the foundation (or an extension), which the site selects per data key in `fetcher:` — the only thing that block is for:
|
|
1829
1848
|
|
|
1830
1849
|
```yaml
|
|
1831
1850
|
# site.yml
|
|
@@ -1841,11 +1860,11 @@ fetcher:
|
|
|
1841
1860
|
|
|
1842
1861
|
⛔ `fetcher.baseUrl`, `headers`, `envelope`, `supports` and `request.*` are **retired**: a third party's conventions belong in a transport, not in the runtime every site loads. The build warns once and ignores them.
|
|
1843
1862
|
|
|
1844
|
-
> **Never put secrets in `site.yml`** — every value in
|
|
1863
|
+
> **Never put secrets in `site.yml` or `queries.yml`** — every value in them is public to the browser. Sites needing private credentials proxy through the same origin at the deployment layer, so the site fetches `/api/…` and the proxy attaches the credential server-side.
|
|
1845
1864
|
|
|
1846
1865
|
**Failures are visible, not empty:** a fetch that failed leaves its key ABSENT from `content.data` and names the message on `block.dataError[key]`; it is never delivered as `[]`, which means "no records". The page still renders — a section reads `dataError` to tell the two apart.
|
|
1847
1866
|
|
|
1848
|
-
When
|
|
1867
|
+
When an external query is enough and when a transport is the answer: `development/data-sources.md`.
|
|
1849
1868
|
|
|
1850
1869
|
Full model: `reference/data-fetching.md`. Where-object format with examples: `authoring/predicates.md`.
|
|
1851
1870
|
|
package/src/framework-index.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-13T23:46:11.125Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/api": {
|
|
6
|
-
"version": "0.3.
|
|
6
|
+
"version": "0.3.4",
|
|
7
7
|
"path": "framework/api",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/core"
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"@uniweb/build": {
|
|
13
|
-
"version": "0.
|
|
13
|
+
"version": "0.48.0",
|
|
14
14
|
"path": "framework/build",
|
|
15
15
|
"deps": [
|
|
16
16
|
"@uniweb/content-reader",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"deps": []
|
|
35
35
|
},
|
|
36
36
|
"@uniweb/core": {
|
|
37
|
-
"version": "0.
|
|
37
|
+
"version": "0.27.0",
|
|
38
38
|
"path": "framework/core",
|
|
39
39
|
"deps": [
|
|
40
40
|
"@uniweb/semantic-parser",
|
|
@@ -47,14 +47,14 @@
|
|
|
47
47
|
"deps": []
|
|
48
48
|
},
|
|
49
49
|
"@uniweb/icons": {
|
|
50
|
-
"version": "0.4.
|
|
50
|
+
"version": "0.4.18",
|
|
51
51
|
"path": "framework/icons",
|
|
52
52
|
"deps": [
|
|
53
53
|
"@uniweb/core"
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
56
|
"@uniweb/kit": {
|
|
57
|
-
"version": "0.18.
|
|
57
|
+
"version": "0.18.5",
|
|
58
58
|
"path": "framework/kit",
|
|
59
59
|
"deps": [
|
|
60
60
|
"@uniweb/core",
|
|
@@ -74,7 +74,7 @@
|
|
|
74
74
|
"deps": []
|
|
75
75
|
},
|
|
76
76
|
"@uniweb/projections": {
|
|
77
|
-
"version": "0.
|
|
77
|
+
"version": "0.7.0",
|
|
78
78
|
"path": "framework/projections",
|
|
79
79
|
"deps": [
|
|
80
80
|
"@uniweb/content-writer",
|
|
@@ -82,7 +82,7 @@
|
|
|
82
82
|
]
|
|
83
83
|
},
|
|
84
84
|
"@uniweb/runtime": {
|
|
85
|
-
"version": "0.
|
|
85
|
+
"version": "0.23.0",
|
|
86
86
|
"path": "framework/runtime",
|
|
87
87
|
"deps": [
|
|
88
88
|
"@uniweb/core",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"deps": []
|
|
121
121
|
},
|
|
122
122
|
"@uniweb/unipress": {
|
|
123
|
-
"version": "0.9.
|
|
123
|
+
"version": "0.9.17",
|
|
124
124
|
"path": "framework/unipress",
|
|
125
125
|
"deps": [
|
|
126
126
|
"@uniweb/build",
|