uniweb 0.83.0 → 0.85.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uniweb",
3
- "version": "0.83.0",
3
+ "version": "0.85.0",
4
4
  "description": "Create structured Vite + React sites with content/code separation",
5
5
  "type": "module",
6
6
  "bin": {
@@ -42,15 +42,15 @@
42
42
  "prompts": "^2.4.2",
43
43
  "tar": "^7.0.0",
44
44
  "@uniweb/content-writer": "^0.3.4",
45
- "@uniweb/runtime": "^0.29.1",
46
- "@uniweb/kit": "^0.19.17",
45
+ "@uniweb/runtime": "^0.29.2",
46
+ "@uniweb/kit": "^0.19.18",
47
47
  "@uniweb/schemas": "^0.12.0",
48
- "@uniweb/core": "^0.37.1",
48
+ "@uniweb/core": "^0.37.2",
49
49
  "@uniweb/semantic-parser": "^1.5.1"
50
50
  },
51
51
  "peerDependencies": {
52
- "@uniweb/build": "^0.76.1",
53
52
  "@uniweb/content-reader": "^1.2.8",
53
+ "@uniweb/build": "^0.78.0",
54
54
  "@uniweb/semantic-parser": "^1.5.1"
55
55
  },
56
56
  "peerDependenciesMeta": {
@@ -1962,15 +1962,15 @@ When an external query is enough and when a transport is the answer: `developmen
1962
1962
 
1963
1963
  Full model: `reference/data-fetching.md`. Where-object format with examples: `authoring/predicates.md`.
1964
1964
 
1965
- ### Search (`search:`)
1965
+ ### Search (`services.search`)
1966
1966
 
1967
- Search follows the same arrangement as `fetcher:` — the **site** declares where results come from, and a search UI reads them the same way regardless. Never hardcode a search endpoint in a component; that couples the foundation to one host.
1967
+ Search follows the same arrangement as `fetcher:` — the **site** declares where results come from, and a search UI reads them the same way regardless. Never hardcode a search endpoint in a component; that couples the foundation to one host. Search is on by default; its entry under `services:` (see *Services*, below) turns it off, sets options, or — on a site you publish — asks the host for it:
1968
1968
 
1969
1969
  ```yaml
1970
1970
  # site.yml
1971
- search:
1972
- enabled: true
1973
- provider: index # default — download an index, match in the browser
1971
+ services:
1972
+ search:
1973
+ provider: index # default — download an index, match in the browser
1974
1974
  ```
1975
1975
 
1976
1976
  | Provider | Answers with | Trade-off |
@@ -1980,12 +1980,13 @@ search:
1980
1980
  | *any other name* | A foundation-supplied search transport | Fully open — Typesense, Meilisearch, Pagefind, a vendor API |
1981
1981
 
1982
1982
  ```yaml
1983
- search:
1984
- provider: endpoint
1985
- endpoint: _search # REQUIRED — there is no default
1983
+ services:
1984
+ search:
1985
+ provider: endpoint
1986
+ endpoint: _search # REQUIRED — there is no default
1986
1987
  ```
1987
1988
 
1988
- `endpoint:` is **required** with `provider: endpoint`; omit it and the provider refuses the query rather than guessing a path. It resolves against the site's base path — `/` → `/_search`, `base: /docs/` → `/docs/_search`, a subpath-served site follows its subpath. An absolute `https://…` URL points at another origin. A host that serves the site may offer search itself, supplying the address so the site declares none.
1989
+ `endpoint:` is **required** with `provider: endpoint`; omit it and the provider refuses the query rather than guessing a path. It resolves against the site's base path — `/` → `/_search`, `base: /docs/` → `/docs/_search`, a subpath-served site follows its subpath. An absolute `https://…` URL points at another origin. A host that serves the site may offer search itself: `search: true` asks for it, and the host supplies the address. An endpoint of your own asks the host to leave its search off, so yours answers.
1989
1990
 
1990
1991
  **Results have one shape, whatever the provider.** Always present: `id`, `type`, `route`, `href`, `title`, `pageTitle`, `excerpt`, `snippetHtml`. Provider-optional (`null` when absent): `sectionId`, `anchor`, `description`, `component`, `snippetText`, `matches`, `group`, `item`. `type` is `page`, `section` or `record`; on a record hit, `item` holds the record's fields and `group` names the set it came from. Whether an optional field arrives is a deployment fact, not a content fact — the same site yields `item` from a server provider and `null` from the local index — so guard them: `result.item?.image`.
1991
1992
 
@@ -2002,26 +2003,27 @@ const { results, isLoading, query } = useSearch(website) // `query` is a funct
2002
2003
 
2003
2004
  Full reference: `authoring/search.md`.
2004
2005
 
2005
- ### Forms (`submit:`)
2006
+ ### Forms (`services.submit`)
2006
2007
 
2007
2008
  Drawing a form is a foundation's job; delivering what a visitor typed needs a
2008
2009
  server. Where that server is comes from the **site or its host** — never from a
2009
- section type, same arrangement as `fetcher:` and `search:`.
2010
+ section type, same arrangement as `fetcher:` and search.
2010
2011
 
2011
2012
  A form gets its destination from the first of these that applies:
2012
2013
 
2013
- 1. **One the host supplies** — `services.submit` in the served payload. Where the
2014
- host handles submissions, that is the destination and nothing in `site.yml`
2015
- overrides it — so a site published to Uniweb Cloud normally needs **no
2016
- `submit:` at all**.
2017
- 2. **`submit:` in `site.yml`** — an endpoint you name yourself, for a host that
2018
- does not handle submissions, or a static site.
2019
- 3. **Neither** — there is no destination, and the form says so instead of
2020
- guessing at one.
2014
+ 1. **One the host supplies** — `config.services.submit` in the payload it serves. Where the
2015
+ host handles submissions, that is the destination. A site published to Uniweb
2016
+ Cloud asks for it with `submit: true` under `services:` and names no address.
2017
+ 2. **An address of the site's own** — `submit: <url>` under `services:`, for a
2018
+ static site or a host that does not handle submissions. On a host that does, it
2019
+ asks the host to leave its own off, so this one answers.
2020
+ 3. **Neither** — there is no destination: render no form, or fall back to contact
2021
+ details the site already carries.
2021
2022
 
2022
- That is the general arrangement, not a forms-only one. A host declares
2023
- everything it offers under `services`, keyed by name, and every service resolves
2024
- by the same rule — the host's offer, then your declaration, then neither.
2023
+ That is the general arrangement, not a forms-only one. A host states everything
2024
+ it offers in the served payload's `config.services`, keyed by name, and every service
2025
+ resolves by the same rule — the host's offer, then the site's own address, then
2026
+ neither.
2025
2027
 
2026
2028
  ⭐ **Before you render UI for a service, ask whether the site has it** — one predicate per service,
2027
2029
  no arguments: `isSearchEnabled()`, `isSubmitEnabled()`, `isApiEnabled()`, `isAssistantEnabled()`,
@@ -2116,10 +2118,15 @@ wherever tracking is configured, with no help from your components. List
2116
2118
  leaving it out does not switch the baseline off.
2117
2119
 
2118
2120
  ```yaml
2119
- # site.yml — only when YOU are providing the endpoint. Publishing to Uniweb
2120
- # Cloud needs nothing here; `uniweb export` and most `deploy --host` targets do.
2121
- submit: /forms # base-relative, resolved like search.endpoint
2122
- submit: https://forms.example.com/intake # or another origin
2121
+ # site.yml
2122
+ services:
2123
+ submit: true # your host's form handling (Uniweb Cloud)
2124
+ ```
2125
+
2126
+ ```yaml
2127
+ # site.yml — when YOU are providing the endpoint: `uniweb export`, most `deploy --host` targets
2128
+ services:
2129
+ submit: https://forms.example.com/intake # or /forms, base-relative like search's endpoint
2123
2130
  ```
2124
2131
 
2125
2132
  ```jsx
@@ -2138,12 +2145,12 @@ if (!canSubmit) return null // nowhere to send — render no form, or fall ba
2138
2145
  ```
2139
2146
 
2140
2147
  > **The framework never invents an endpoint — but a host may supply one.** Don't
2141
- > reach for `submit:` reflexively: on Uniweb Cloud it is redundant, and setting
2142
- > it there overrides what the platform provides. Reach for it when you are the
2143
- > one hosting.
2148
+ > write an address reflexively: on Uniweb Cloud an address asks the host to turn its
2149
+ > own form handling off — `submit: true` is the whole declaration there. Reach for an
2150
+ > address when you are the one hosting.
2144
2151
  >
2145
- > `canSubmit` is false only when neither a declaration nor a host supplies a
2146
- > destination. **Check it when you render, not only on the button press** — a
2152
+ > `canSubmit` is false only when neither the host nor an address of the site's own
2153
+ > supplies a destination. **Check it when you render, not only on the button press** — a
2147
2154
  > form nobody can send should not be on the page at all. A read that 404s
2148
2155
  > degrades to `[]` and the page still renders; a write that 404s loses what a
2149
2156
  > person typed, so it gets no silent fallback.
@@ -2197,7 +2204,7 @@ from. `values` keeps the `File` so your input can show its selection.
2197
2204
 
2198
2205
  Full reference: `development/receiving-form-submissions.md`.
2199
2206
 
2200
- ### Tracking (`tracking:`)
2207
+ ### Tracking (`services.tracking`)
2201
2208
 
2202
2209
  A site may declare one **tracking destination**, and everything worth counting
2203
2210
  goes there as an event on a single stream. A page visit is just the event the
@@ -2205,23 +2212,28 @@ runtime emits by itself.
2205
2212
 
2206
2213
  ```yaml
2207
2214
  # site.yml — your own collector, on any host
2208
- tracking: https://collector.example.com/events
2215
+ services:
2216
+ tracking: https://collector.example.com/events
2217
+ ```
2209
2218
 
2219
+ ```yaml
2210
2220
  # or, when it needs more than an address
2211
- tracking:
2212
- endpoint: /collect
2213
- consent: required
2221
+ services:
2222
+ tracking:
2223
+ endpoint: /collect
2224
+ consent: required
2214
2225
  ```
2215
2226
 
2216
- A host may also supply one under `services.tracking`, and the usual precedence
2217
- applies: the host's, then yours, then neither.
2227
+ A host may also supply one — `tracking: true` asks for it on a site you publish —
2228
+ and the usual precedence applies: the host's, then yours, then neither. An
2229
+ endpoint of your own asks the host to leave its collector off, so yours answers.
2218
2230
 
2219
2231
  ⚠️ **The endpoint has to accept the framework's own format** — a batched
2220
2232
  `{ "events": [ … ] }` POST, documented in `reference/site-configuration.md`. It
2221
2233
  is not a third-party analytics product's public API, which expects that
2222
2234
  product's own shape.
2223
2235
 
2224
- A site may also name a vendor's own script under `tracking.scripts`, which the
2236
+ A site may also name a vendor's own script under `services.tracking.scripts`, which the
2225
2237
  runtime loads once, after consent when a gate is declared, and never in a frame
2226
2238
  or during prerender. That is a **separate path with no connection to the stream
2227
2239
  below** — the vendor measures its own way, and nothing you `track()` reaches it.
@@ -2271,21 +2283,24 @@ Narrow or widen that with `emit`:
2271
2283
 
2272
2284
  ```yaml
2273
2285
  # site.yml — your own collector
2274
- tracking:
2275
- endpoint: https://collector.example.com/events
2276
- emit: standard # minimal | standard | all — or a list of event names
2286
+ services:
2287
+ tracking:
2288
+ endpoint: https://collector.example.com/events
2289
+ emit: standard # minimal | standard | all — or a list of event names
2290
+ ```
2277
2291
 
2292
+ ```yaml
2278
2293
  # site.yml — a host that supplies the collector: say what to send, not where
2279
- tracking:
2280
- emit: minimal
2294
+ services:
2295
+ tracking:
2296
+ emit: minimal
2281
2297
  ```
2282
2298
 
2283
2299
  ⭐ **`emit` needs no endpoint of its own.** Where a host provides one, the site
2284
2300
  declares only what it wants sent and the address comes from the host. The two
2285
2301
  are read key by key, so naming `emit` alone overrides nothing else the host
2286
- declared. An `endpoint:` of your own is used wherever the host supplies no
2287
- collector — on a host without one, and on none — and where the host supplies
2288
- one, the host's is used.
2302
+ declared. An `endpoint:` of your own means you collect yourself, and asks the host
2303
+ to leave its collector off.
2289
2304
 
2290
2305
  `minimal` is `page_view` alone. `standard` is the default when the collector is
2291
2306
  your own. `all` is a standing yes, so an event added in a later framework
@@ -2540,8 +2555,9 @@ permission model and a CSS one.
2540
2555
  You do not need a live backend to build against one. In `site.yml`:
2541
2556
 
2542
2557
  ```yaml
2543
- api: /_api # where it answers — the same value in production
2544
- $devApi: ./mock/api.js # what answers it locally; `$` keys are never published
2558
+ services:
2559
+ api: true # ask your host for an app backend when you publish
2560
+ $devApi: ./mock/api.js # what answers it in `uniweb dev`; `$` keys are never published
2545
2561
  ```
2546
2562
 
2547
2563
  ```js
@@ -2550,8 +2566,10 @@ import { createMockBackend } from '@uniweb/api/mock'
2550
2566
  export default createMockBackend({ seed }).fetch
2551
2567
  ```
2552
2568
 
2553
- `uniweb dev` mounts it at your `api:` address, same-origin, so cookies and your
2554
- site's configuration behave exactly as they will in production. It **enforces** who
2569
+ `uniweb dev` answers your site's `api` service with it, at an address the dev server
2570
+ supplies on its own origin, so cookies behave exactly as they will in production. Write
2571
+ no address of your own for it: under `services:` an address means a backend you run, and
2572
+ asks your host to leave its own off. It **enforces** who
2555
2573
  may edit an entry and `append_only`, so a permission you are relying on fails on your
2556
2574
  machine rather than in front of a user. State is in memory; restart to reset.
2557
2575
 
@@ -2657,6 +2675,36 @@ Foundations have their own free path too: `uniweb add ci --target foundation` pu
2657
2675
 
2658
2676
  **`sync.json` says which site this is, on each backend.** The first push to a backend records what that backend assigned — the site's id and owner, the ids of its records and uploaded files — in `sync.json` beside `site.yml`. Commit it; never edit it. **To make a new site from a copy of a project, run `uniweb forget --all` in the copy before its first push** — the copy carries the original's `sync.json`, so otherwise its push updates the original's site. When two projects in one workspace hold the same site, a push or publish from either is refused until that is done. `uniweb forget --backend <url>` removes just one backend's records, such as a scratch server's. A record file's `$uuid` is its own id and stays in both cases. **`push`, `pull` and `publish` go to the backend you are logged in to** — the last `uniweb login --backend <url>` — and never to one you are not logged in to. A bare `uniweb login` logs in to https://uniweb.app; for any other backend, name it — logging in is how you switch, and the backend commands take no `--backend` of their own. When the project has no site on that backend but has one elsewhere, a push says so before creating a new one.
2659
2677
 
2678
+ **`services:` in `site.yml` is where a site says which services it uses** — site search, form
2679
+ handling, analytics, an assistant, accounts — one entry per service:
2680
+
2681
+ ```yaml
2682
+ services:
2683
+ search: true # on — your host's, or the built-in index
2684
+ submit: false # off
2685
+ tracking: https://collector.example.com/events # a provider you bring
2686
+ api: # on, with the service's own settings
2687
+ grade: pro
2688
+ ```
2689
+
2690
+ An entry is `true`, `false`, an address of your own (a string, or `endpoint:` in a map), or a map
2691
+ of options. On a site you push or publish it is also **what you ask your host for**: `true` asks for
2692
+ its service, `false` asks it to turn its service off, and an address asks it to leave its own off so
2693
+ yours answers. **On a site your host publishes, a service is off unless `services:` asks for it** —
2694
+ list the ones the site uses (the templates list theirs). `records: true` asks for your records to be
2695
+ delivered live to the published pages: a query whose records have a data schema shows nothing there
2696
+ without it, and `publish` says so; a static build reads records from files and ignores it. An entry
2697
+ says everything about its service: a setting you remove from it is removed on the next push, and
2698
+ removing the entry switches the service off. `uniweb push` and `uniweb publish` send each service
2699
+ `site.yml` lists; a service the site has that this copy has never seen (turned on in the app since
2700
+ your last pull) is left as it is, and one changed both in the file and on the site since your last
2701
+ pull stops the push, naming it — `uniweb pull --merge`, then push. `uniweb pull` writes what the site
2702
+ has into `services:` (an off service with no settings only where the file already names it) — pull
2703
+ before you edit. A change that needs payment is settled in the app: `publish` opens it. **Everything in an entry but a
2704
+ credential is public** — it is built into the site, except `api`'s settings, which only your host
2705
+ reads; a key or token is set in the app. ⛔ The top-level `search:` / `submit:` / `assistant:` /
2706
+ `tracking:` / `api:` keys are retired: the build stops on them and says where each one moves.
2707
+
2660
2708
  **The Cloud also provides a real backend for structured data:** a database for every registered data schema, and a CMS that edits both static page content and dynamic data entities typed by those schemas. That's the piece that makes it viable for teams and client work — the client manages records, not markdown files.
2661
2709
 
2662
2710
  Either side can publish. Nothing about this changes how you build: the same foundation and the same site run under `uniweb dev`, `uniweb export`, or a CI deploy with no account at all.