uniweb 0.48.2 → 0.48.4
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 +42 -20
- package/src/commands/doctor.js +17 -7
- package/src/framework-index.json +8 -8
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uniweb",
|
|
3
|
-
"version": "0.48.
|
|
3
|
+
"version": "0.48.4",
|
|
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/core": "^0.24.
|
|
45
|
-
"@uniweb/kit": "^0.18.
|
|
46
|
-
"@uniweb/
|
|
47
|
-
"@uniweb/
|
|
44
|
+
"@uniweb/core": "^0.24.3",
|
|
45
|
+
"@uniweb/kit": "^0.18.1",
|
|
46
|
+
"@uniweb/runtime": "^0.19.4",
|
|
47
|
+
"@uniweb/semantic-parser": "^1.4.0"
|
|
48
48
|
},
|
|
49
49
|
"peerDependencies": {
|
|
50
|
-
"@uniweb/build": "^0.44.1",
|
|
51
50
|
"@uniweb/content-reader": "^1.2.4",
|
|
52
|
-
"@uniweb/semantic-parser": "^1.4.0"
|
|
51
|
+
"@uniweb/semantic-parser": "^1.4.0",
|
|
52
|
+
"@uniweb/build": "^0.44.3"
|
|
53
53
|
},
|
|
54
54
|
"peerDependenciesMeta": {
|
|
55
55
|
"@uniweb/build": {
|
package/partials/agents.md
CHANGED
|
@@ -716,7 +716,7 @@ Measured on exactly that shape: **the agent corpus holds every page; the public
|
|
|
716
716
|
- **A static host** (`uniweb export`, `deploy --host`) drops every page — they have no reader there — and you get an empty SPA shell. The build still says *"complete"*, because 0 pages is not an error: it reports `Collected 0 pages` and pre-renders none.
|
|
717
717
|
- **A backend-hosted deployment that does not offer the service.** Whether an agent endpoint exists is the host's to decide, per site — it is not implied by deploying successfully.
|
|
718
718
|
|
|
719
|
-
⇒ **The way to know is to ask, at render:** `
|
|
719
|
+
⇒ **The way to know is to ask, at render:** `isAssistantEnabled()` from `@uniweb/kit` is true only where an agent endpoint is declared. On a site that is *only* knowledge there is no component to ask — so confirm with your host that the agent is enabled for that site before you build an integration against it. If you meant to build an agent endpoint and got silence, this is where to look.
|
|
720
720
|
|
|
721
721
|
### Your site is readable by agents, automatically
|
|
722
722
|
|
|
@@ -1904,7 +1904,26 @@ A form gets its destination from the first of these that applies:
|
|
|
1904
1904
|
|
|
1905
1905
|
That is the general arrangement, not a forms-only one. A host declares
|
|
1906
1906
|
everything it offers under `services`, keyed by name, and every service resolves
|
|
1907
|
-
by the same rule — your declaration, then the host's, then neither
|
|
1907
|
+
by the same rule — your declaration, then the host's, then neither.
|
|
1908
|
+
|
|
1909
|
+
⭐ **Before you render UI for a service, ask whether the site has it** — one predicate per service,
|
|
1910
|
+
no arguments: `isSearchEnabled()`, `isSubmitEnabled()`, `isApiEnabled()`, `isAssistantEnabled()`,
|
|
1911
|
+
`isTrackingEnabled()`.
|
|
1912
|
+
|
|
1913
|
+
```jsx
|
|
1914
|
+
import { isSearchEnabled } from '@uniweb/kit'
|
|
1915
|
+
|
|
1916
|
+
if (!isSearchEnabled()) return null // false ⇒ draw nothing
|
|
1917
|
+
```
|
|
1918
|
+
|
|
1919
|
+
Each answers the same question — *would UI for this service work on this site?* — and `false`
|
|
1920
|
+
always means the same thing: **draw nothing.** `isSearchEnabled()` is true whenever *any* provider
|
|
1921
|
+
answers, including the prebuilt index a static site ships, so a search box gated on it appears
|
|
1922
|
+
wherever search works. The hooks that draw a feature hand you the same answer as a field
|
|
1923
|
+
(`useSearch().isEnabled`, `useFormSubmit().canSubmit`), so a component already using one needs
|
|
1924
|
+
nothing extra.
|
|
1925
|
+
|
|
1926
|
+
When you need the **address** itself, not just whether one exists, ask for it:
|
|
1908
1927
|
|
|
1909
1928
|
```jsx
|
|
1910
1929
|
import { resolveService } from '@uniweb/kit'
|
|
@@ -1914,7 +1933,8 @@ const { url, source } = resolveService(website, 'assistant') // or 'search', o
|
|
|
1914
1933
|
|
|
1915
1934
|
**The name is open**: the framework ships clients for what it implements and
|
|
1916
1935
|
resolution for anything, so a foundation can define a service the framework has
|
|
1917
|
-
never heard of and a host can fill it
|
|
1936
|
+
never heard of and a host can fill it — ask for it with
|
|
1937
|
+
`website.isServiceEnabled('booking')`. Same escalation `fetcher.transports`
|
|
1918
1938
|
offers for data.
|
|
1919
1939
|
|
|
1920
1940
|
**Building an "Ask AI" component?** The service name is `assistant`, and a host that runs an agent for a site typically serves it at the conventional path `/_agent`. **You should never need to write that path** — ask the runtime instead:
|
|
@@ -1936,7 +1956,7 @@ if (!url) return null // this site has no agent — render nothing, or
|
|
|
1936
1956
|
|
|
1937
1957
|
### Declaring what your foundation supports
|
|
1938
1958
|
|
|
1939
|
-
`resolveService
|
|
1959
|
+
The predicates above, and `resolveService`, are how you ask at render time. The other direction — telling a
|
|
1940
1960
|
host, *before* anything renders, which services your foundation is built to use —
|
|
1941
1961
|
is one line in the foundation's `package.json`:
|
|
1942
1962
|
|
|
@@ -1953,23 +1973,24 @@ against it.** A host that offers search has no way to know whether your sections
|
|
|
1953
1973
|
draw a search box, so without this it either offers a site something its code
|
|
1954
1974
|
will ignore, or withholds something it would have used.
|
|
1955
1975
|
|
|
1956
|
-
**
|
|
1976
|
+
**What a host receives is one of three answers:**
|
|
1957
1977
|
|
|
1958
1978
|
| | |
|
|
1959
1979
|
|---|---|
|
|
1960
|
-
|
|
|
1961
|
-
| `
|
|
1962
|
-
|
|
|
1980
|
+
| a list — `["search"]` | the services this foundation renders against, and only these |
|
|
1981
|
+
| `[]` | none — proven by the build, not assumed |
|
|
1982
|
+
| absent | *unknown* — the build could not tell, and nothing was declared |
|
|
1963
1983
|
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1984
|
+
⭐ **The build reads the set off your code.** When the foundation is built, `supports` is derived
|
|
1985
|
+
from what the bundle actually reaches — `resolveService(website, 'search')`, or a predicate like
|
|
1986
|
+
`isSearchEnabled()` — so a foundation that never writes the key still publishes an accurate set.
|
|
1987
|
+
What you write in `package.json` is a **supplement**: the build publishes the union, so a
|
|
1988
|
+
declaration can add a service but never remove one the code reaches.
|
|
1967
1989
|
|
|
1968
|
-
**
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
cannot promise it found them all. The declaration is yours to keep accurate.
|
|
1990
|
+
**Declare only what the build cannot see.** A service reached through a *computed* name —
|
|
1991
|
+
`resolveService(website, name)` where `name` is a variable — is invisible to it, and the build
|
|
1992
|
+
warns when that happens. List those. `uniweb doctor` compares what you declared with what the build
|
|
1993
|
+
found.
|
|
1973
1994
|
|
|
1974
1995
|
⚖️ **Baseline behaviour is not yours to declare.** Some services do something for
|
|
1975
1996
|
a site whether or not a foundation cooperates — the runtime reports page views
|
|
@@ -2253,8 +2274,8 @@ happened into a journey.
|
|
|
2253
2274
|
Everything above reaches the runtime through `@uniweb/kit` or through something
|
|
2254
2275
|
handed to your component as a prop. That is the rule, not a stylistic preference:
|
|
2255
2276
|
|
|
2256
|
-
- ✅ `useWebsite()`, `useTracker()`, `
|
|
2257
|
-
utilities.
|
|
2277
|
+
- ✅ `useWebsite()`, `useTracker()`, `isSearchEnabled()` and the other service
|
|
2278
|
+
predicates, `resolveService(website, …)` — kit hooks and utilities.
|
|
2258
2279
|
- ✅ `block.track(…)`, `block.page`, `block.website` — the block **arrives in your
|
|
2259
2280
|
props**, so calling methods on it is not reaching for a global.
|
|
2260
2281
|
- ⛔ `globalThis.uniweb`, `window.uniweb` — never, in a foundation.
|
|
@@ -2317,9 +2338,10 @@ npm install @uniweb/api # in the FOUNDATION, beside @uniweb/kit
|
|
|
2317
2338
|
### Ask before you draw
|
|
2318
2339
|
|
|
2319
2340
|
```jsx
|
|
2320
|
-
import {
|
|
2341
|
+
import { isApiEnabled } from '@uniweb/kit'
|
|
2342
|
+
import { useSession, SignedIn, SignedOut } from '@uniweb/api'
|
|
2321
2343
|
|
|
2322
|
-
if (!
|
|
2344
|
+
if (!isApiEnabled()) return <StaticVersion /> // synchronous — nothing to await
|
|
2323
2345
|
```
|
|
2324
2346
|
|
|
2325
2347
|
⛔ **When there is no backend, draw nothing** — not a disabled control, and not an
|
package/src/commands/doctor.js
CHANGED
|
@@ -506,13 +506,13 @@ export async function checkFoundationSupports({
|
|
|
506
506
|
* box that searches nothing, a form whose answers have nowhere to go.
|
|
507
507
|
*/
|
|
508
508
|
const SERVICE_GATES = [
|
|
509
|
-
{ hook: 'useFormSubmit', gate: 'canSubmit', draws: 'a form' },
|
|
510
|
-
{ hook: 'useSearch', gate: 'isEnabled', draws: 'a search control' },
|
|
511
|
-
{ hook: 'useSearchIndex', gate: 'isEnabled', draws: 'a search control' },
|
|
509
|
+
{ hook: 'useFormSubmit', gate: 'canSubmit', alt: 'isSubmitEnabled', draws: 'a form' },
|
|
510
|
+
{ hook: 'useSearch', gate: 'isEnabled', alt: 'isSearchEnabled', draws: 'a search control' },
|
|
511
|
+
{ hook: 'useSearchIndex', gate: 'isEnabled', alt: 'isSearchEnabled', draws: 'a search control' },
|
|
512
512
|
// Returns everything `useSearch` does, `isEnabled` included, so it gates the
|
|
513
513
|
// same way. It was missing from this list until the hook was renamed out of
|
|
514
514
|
// `useSearchWithIntent` — the kind of gap a list of names grows quietly.
|
|
515
|
-
{ hook: 'useSearchPrefetch', gate: 'isEnabled', draws: 'a search control' },
|
|
515
|
+
{ hook: 'useSearchPrefetch', gate: 'isEnabled', alt: 'isSearchEnabled', draws: 'a search control' },
|
|
516
516
|
]
|
|
517
517
|
|
|
518
518
|
/**
|
|
@@ -535,7 +535,12 @@ const SERVICE_GATES = [
|
|
|
535
535
|
*
|
|
536
536
|
* - it is **local to one file** — the hook call and its gate are in the same
|
|
537
537
|
* component, in the developer's own source, with no indirection to follow;
|
|
538
|
-
* - the gate names are **fixed by kit's API**, not inferred
|
|
538
|
+
* - the gate names are **fixed by kit's API**, not inferred — with one caveat
|
|
539
|
+
* that used to be a live false negative: `isEnabled` is a hook's returned
|
|
540
|
+
* field AND, until 2026-09-10, an `@uniweb/api` export. A component holding
|
|
541
|
+
* both read as gated. `@uniweb/kit`'s `is<Service>Enabled()` predicates are
|
|
542
|
+
* unique per subject, so `alt` below is matched instead and the collision
|
|
543
|
+
* cannot recur;
|
|
539
544
|
* - it only ever **warns**, and changes no artifact. A miss costs a missing
|
|
540
545
|
* warning, which is the safe direction.
|
|
541
546
|
*
|
|
@@ -568,11 +573,15 @@ export function checkUngatedServiceControls({ foundationName, folderName, srcDir
|
|
|
568
573
|
|
|
569
574
|
forEachSourceFile(srcDir, (raw, file) => {
|
|
570
575
|
const text = stripComments(raw)
|
|
571
|
-
for (const { hook, gate, draws } of SERVICE_GATES) {
|
|
576
|
+
for (const { hook, gate, alt, draws } of SERVICE_GATES) {
|
|
572
577
|
// A call, not merely an import: re-exporting a hook is not drawing with it.
|
|
573
578
|
if (!new RegExp(`\\b${hook}\\s*\\(`).test(text)) continue
|
|
579
|
+
// Either the hook's own returned field, or kit's standalone predicate for
|
|
580
|
+
// the same service — both are the gate, and a component that reads one
|
|
581
|
+
// does not need the other.
|
|
574
582
|
if (new RegExp(`\\b${gate}\\b`).test(text)) continue
|
|
575
|
-
|
|
583
|
+
if (alt && new RegExp(`\\b${alt}\\s*\\(`).test(text)) continue
|
|
584
|
+
found.push({ file, hook, gate, alt, draws })
|
|
576
585
|
}
|
|
577
586
|
})
|
|
578
587
|
|
|
@@ -592,6 +601,7 @@ export function checkUngatedServiceControls({ foundationName, folderName, srcDir
|
|
|
592
601
|
log(` control cannot work. Gate on it instead:`)
|
|
593
602
|
log(` ${colors.dim}const { ${f.gate} } = ${f.hook}(…)${colors.reset}`)
|
|
594
603
|
log(` ${colors.dim}if (!${f.gate}) return null${colors.reset}`)
|
|
604
|
+
if (f.alt) log(` ${colors.dim}…or ${f.alt}() from @uniweb/kit, which needs no hook.${colors.reset}`)
|
|
595
605
|
}
|
|
596
606
|
}
|
|
597
607
|
|
package/src/framework-index.json
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": 1,
|
|
3
|
-
"generatedAt": "2026-09-
|
|
3
|
+
"generatedAt": "2026-09-10T14:59:00.380Z",
|
|
4
4
|
"packages": {
|
|
5
5
|
"@uniweb/api": {
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.3.0",
|
|
7
7
|
"path": "framework/api",
|
|
8
8
|
"deps": [
|
|
9
9
|
"@uniweb/core"
|
|
10
10
|
]
|
|
11
11
|
},
|
|
12
12
|
"@uniweb/build": {
|
|
13
|
-
"version": "0.44.
|
|
13
|
+
"version": "0.44.3",
|
|
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.24.
|
|
37
|
+
"version": "0.24.3",
|
|
38
38
|
"path": "framework/core",
|
|
39
39
|
"deps": [
|
|
40
40
|
"@uniweb/semantic-parser",
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
]
|
|
55
55
|
},
|
|
56
56
|
"@uniweb/kit": {
|
|
57
|
-
"version": "0.18.
|
|
57
|
+
"version": "0.18.1",
|
|
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.6.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.19.
|
|
85
|
+
"version": "0.19.4",
|
|
86
86
|
"path": "framework/runtime",
|
|
87
87
|
"deps": [
|
|
88
88
|
"@uniweb/core",
|
|
@@ -110,7 +110,7 @@
|
|
|
110
110
|
"deps": []
|
|
111
111
|
},
|
|
112
112
|
"@uniweb/templates": {
|
|
113
|
-
"version": "0.12.
|
|
113
|
+
"version": "0.12.3",
|
|
114
114
|
"path": "framework/templates",
|
|
115
115
|
"deps": []
|
|
116
116
|
},
|