@uniweb/kit 0.13.4 → 0.13.5
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/kit",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.5",
|
|
4
4
|
"description": "Standard component library for Uniweb foundations",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"exports": {
|
|
@@ -44,8 +44,8 @@
|
|
|
44
44
|
"shiki": "^3.0.0",
|
|
45
45
|
"tailwind-merge": "^3.6.0",
|
|
46
46
|
"temml": "^0.13.2",
|
|
47
|
-
"@uniweb/core": "^0.
|
|
48
|
-
"@uniweb/semantic-parser": "^1.3.
|
|
47
|
+
"@uniweb/core": "^0.12.0",
|
|
48
|
+
"@uniweb/semantic-parser": "^1.3.1",
|
|
49
49
|
"@uniweb/scene": "^0.1.3"
|
|
50
50
|
},
|
|
51
51
|
"peerDependencies": {
|
package/src/search/client.js
CHANGED
|
@@ -10,12 +10,28 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Resolution order:
|
|
12
12
|
* 1. `site.yml search.provider` — the author's explicit choice
|
|
13
|
-
* 2.
|
|
13
|
+
* 2. the HOST's offer — `config.services.search` in the payload,
|
|
14
|
+
* read through `resolveService` → `endpoint`
|
|
15
|
+
* 3. `'index'` — the free default, works on any host
|
|
14
16
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
17
|
+
* ⛔ Tier 2 SHIPPED. This block described it as "a future tier … deliberately
|
|
18
|
+
* not invented here" until 2026-08-25, while `resolveService` was imported at
|
|
19
|
+
* the top of this file and read ~100 lines below. The wire key is
|
|
20
|
+
* `config.services.search`, resolved by `@uniweb/core/services` on the same
|
|
21
|
+
* two-tier rule every site service uses (site-authored wins, host fills gaps).
|
|
22
|
+
*
|
|
23
|
+
* ⚠️ That stale sentence had a cost outside this repo. The hosting lane could
|
|
24
|
+
* not tell from reading whether kit consumed the block, asked in a collab
|
|
25
|
+
* channel, and the answer mattered: on the sync/link lane the framework emits
|
|
26
|
+
* NO search index at all (removed 2026-08-01, `@uniweb/build`
|
|
27
|
+
* `site/build-site-data.js`), so tier 3 requests a `search-index.json` that
|
|
28
|
+
* nothing on that lane writes. A reader of this comment concluded the host tier
|
|
29
|
+
* did not exist and the block was decoration; both halves were wrong.
|
|
30
|
+
*
|
|
31
|
+
* ⭐ So tier 3 is "the free default" only where something EMITS the index —
|
|
32
|
+
* the bundle lane and any static host. It is not a universal fallback, and a
|
|
33
|
+
* host that answers search at its own address must be declared for kit to
|
|
34
|
+
* reach it.
|
|
19
35
|
*
|
|
20
36
|
* Provider contract (duck-typed, matching how the FetcherDispatcher treats
|
|
21
37
|
* transports):
|
|
@@ -126,6 +142,35 @@ export function createSearchClient(website, options = {}) {
|
|
|
126
142
|
// host serves none.
|
|
127
143
|
const authoredProvider = website?.config?.search?.provider
|
|
128
144
|
|
|
145
|
+
// ⛔ THE HOST ANSWERED AND DECLINED. `resolveService` returns
|
|
146
|
+
// `{ url: null, source: 'host' }` when a host declares the service NAME
|
|
147
|
+
// while offering no address — the decline shape `@uniweb/core/services`
|
|
148
|
+
// documents ("absence is a behavioural decision rather than a message").
|
|
149
|
+
//
|
|
150
|
+
// Falling through to `'index'` here would be wrong twice: the host has said
|
|
151
|
+
// it does not serve search, and on a host-served lane the framework emits no
|
|
152
|
+
// index to fall back to, so the fallback resolves to a 404. Draw nothing
|
|
153
|
+
// instead — `isEnabled()` reports false and `query()` returns empty without
|
|
154
|
+
// a request.
|
|
155
|
+
//
|
|
156
|
+
// ⚠️ Only a DECLINE does this. `source: null` — nobody declared the service
|
|
157
|
+
// at all — still means `'index'`, which is correct and is the static-host
|
|
158
|
+
// path: there the build DOES emit the index.
|
|
159
|
+
//
|
|
160
|
+
// ⛔ DO NOT ADD A FALLBACK THAT FIRES ON THIS SHAPE. The contract, settled
|
|
161
|
+
// with a host implementation 2026-08-25: **a host that OFFERS a service must
|
|
162
|
+
// declare it WITH an address** — offered is `{endpoint}`, declined is `{}`.
|
|
163
|
+
// Routing `{}` to `endpoint-provider`'s `DEFAULT_ENDPOINT` looks like a free
|
|
164
|
+
// saving and makes an offered service indistinguishable from a declined one:
|
|
165
|
+
// no search box, no error, nothing to grep for.
|
|
166
|
+
//
|
|
167
|
+
// ⭐ A site's OWN declaration still wins — `resolveService` answers tier 1
|
|
168
|
+
// with `source: 'site'`, so an operator running self-hosted search on a host
|
|
169
|
+
// that does not sell it is untouched. Pinned in
|
|
170
|
+
// `tests/search-host-decline.test.js`; that is the case worth re-running if
|
|
171
|
+
// this branch is ever edited, because the headline case never breaks.
|
|
172
|
+
const hostDeclined = service.source === 'host' && !service.url
|
|
173
|
+
|
|
129
174
|
const declared =
|
|
130
175
|
providerOverride ||
|
|
131
176
|
authoredProvider ||
|
|
@@ -193,11 +238,21 @@ export function createSearchClient(website, options = {}) {
|
|
|
193
238
|
|
|
194
239
|
return {
|
|
195
240
|
/**
|
|
196
|
-
* Check if search is enabled
|
|
241
|
+
* Check if search is enabled.
|
|
242
|
+
*
|
|
243
|
+
* Two independent ways it is off, and a caller needs neither to tell them
|
|
244
|
+
* apart: the SITE disabled it (`search: false` / `search: { enabled:
|
|
245
|
+
* false }`), or the HOST declared the service and offered no address —
|
|
246
|
+
* see `hostDeclined` above. Either way a foundation draws no search UI.
|
|
247
|
+
*
|
|
248
|
+
* ⛔ Deliberately no reason string. Which services a site is provisioned
|
|
249
|
+
* for is not a visitor's business and could not be localized from a public
|
|
250
|
+
* package anyway — the rule `@uniweb/core/services` states at length.
|
|
251
|
+
*
|
|
197
252
|
* @returns {boolean}
|
|
198
253
|
*/
|
|
199
254
|
isEnabled() {
|
|
200
|
-
return website.isSearchEnabled()
|
|
255
|
+
return website.isSearchEnabled() && !hostDeclined
|
|
201
256
|
},
|
|
202
257
|
|
|
203
258
|
/**
|
|
@@ -273,6 +328,13 @@ export function createSearchClient(website, options = {}) {
|
|
|
273
328
|
return { results: [], total: 0 }
|
|
274
329
|
}
|
|
275
330
|
|
|
331
|
+
// The host declared the service and offered no address. Return empty
|
|
332
|
+
// rather than requesting anything — the alternative is a fetch we
|
|
333
|
+
// already know the answer to. Silent: a foundation that checked
|
|
334
|
+
// `isEnabled()` never reaches this, and one that did not should not fill
|
|
335
|
+
// a visitor's console over a service the site was never provisioned for.
|
|
336
|
+
if (hostDeclined) return { results: [], total: 0 }
|
|
337
|
+
|
|
276
338
|
const opts = { limit, type, route, signal }
|
|
277
339
|
|
|
278
340
|
try {
|
|
@@ -16,7 +16,29 @@
|
|
|
16
16
|
import { emptyResult } from './result.js'
|
|
17
17
|
import { resolveServiceUrl } from '../../utils/services.js'
|
|
18
18
|
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Path used when a site declares `provider: endpoint` without an `endpoint:`.
|
|
21
|
+
*
|
|
22
|
+
* ⛔ THIS COVERS THE AUTHORED TIER ONLY — it is unreachable for a HOST-declared
|
|
23
|
+
* service, and it must stay that way.
|
|
24
|
+
*
|
|
25
|
+
* A host that declares the service name while offering no address is a
|
|
26
|
+
* **decline** (`@uniweb/core/services`), and `client.js` short-circuits on it
|
|
27
|
+
* before any provider is selected. So `{ "search": {} }` from a host means
|
|
28
|
+
* "draw nothing", never "use the default".
|
|
29
|
+
*
|
|
30
|
+
* ⇒ **The contract, settled with a host implementation 2026-08-25: a host that
|
|
31
|
+
* OFFERS a service must declare it WITH an address.** Offered is `{endpoint}`;
|
|
32
|
+
* declined is `{}`. The two are one byte apart and the failure is silent — an
|
|
33
|
+
* entitled site drawing no search box.
|
|
34
|
+
*
|
|
35
|
+
* ⚠️ **The saving that looks available from either side, and is not.** From
|
|
36
|
+
* here: *"a host need not send an address, kit defaults to `_search` anyway."*
|
|
37
|
+
* From the emitter: *"why emit `/_search` when kit defaults to it?"* **Both
|
|
38
|
+
* collapse the two states into one.** It was proposed and withdrawn the day
|
|
39
|
+
* this was written, by the lane that had just shipped the decline branch — the
|
|
40
|
+
* hazard does not look like a hazard while you are authoring it.
|
|
41
|
+
*/
|
|
20
42
|
const DEFAULT_ENDPOINT = '_search'
|
|
21
43
|
|
|
22
44
|
/**
|
|
@@ -126,16 +148,30 @@ export function createEndpointProvider(website, options = {}) {
|
|
|
126
148
|
})
|
|
127
149
|
|
|
128
150
|
if (!response.ok) {
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
151
|
+
// ⛔ A HOST'S DECLINE TEXT IS A DIAGNOSTIC, NEVER VISITOR COPY.
|
|
152
|
+
//
|
|
153
|
+
// This block used to argue the opposite — that an endpoint "explains
|
|
154
|
+
// why, in words meant for a visitor", so kit should carry that sentence
|
|
155
|
+
// up for a foundation to render. That is wrong, and it was ruled
|
|
156
|
+
// against on 2026-08-25: a control for a service the site does not have
|
|
157
|
+
// must not be DRAWN in the first place, so there is no place for an
|
|
158
|
+
// apology and nothing for a visitor to read. A site with no search is
|
|
159
|
+
// not broken; it simply has no search, the same way it has no contact
|
|
160
|
+
// form when submissions are not enabled.
|
|
161
|
+
//
|
|
162
|
+
// Two reasons it cannot be visitor copy even if one wanted it to be:
|
|
163
|
+
// it reports the operator's provisioning state to the public, and it
|
|
164
|
+
// arrives in one language from a service layer, bypassing the site's
|
|
165
|
+
// localization entirely. Any text a visitor reads is site content.
|
|
166
|
+
//
|
|
167
|
+
// ⇒ The message is kept because a developer reading a console needs
|
|
168
|
+
// more than "403". `isEnabled()` is what a foundation asks, and it is
|
|
169
|
+
// false long before this line runs.
|
|
133
170
|
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
136
|
-
//
|
|
137
|
-
//
|
|
138
|
-
// something a person understands instead of a number.
|
|
171
|
+
// ⚠️ Ordering, if the local-index fallback in `client.js` is ever
|
|
172
|
+
// removed: that fallback is currently what stops this string
|
|
173
|
+
// propagating, so removing it would surface this message where nothing
|
|
174
|
+
// renders one today. Settle where a provider error may surface FIRST.
|
|
139
175
|
let declined
|
|
140
176
|
try {
|
|
141
177
|
declined = (await response.json())?.error
|