@uniweb/build 0.39.0 → 0.41.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/build",
3
- "version": "0.39.0",
3
+ "version": "0.41.0",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -19,7 +19,6 @@
19
19
  "./prerender": "./src/prerender.js",
20
20
  "./hosts": "./src/hosts/index.js",
21
21
  "./i18n": "./src/i18n/index.js",
22
- "./search": "./src/search/index.js",
23
22
  "./uwx": "./src/uwx/index.js",
24
23
  "./import-map-plugin": "./src/import-map-plugin.js"
25
24
  },
@@ -57,15 +56,15 @@
57
56
  "js-yaml": "^4.1.0",
58
57
  "sharp": "^0.35.3",
59
58
  "yaml": "^2.5.0",
59
+ "@uniweb/content-reader": "^1.2.4",
60
60
  "@uniweb/content-writer": "^0.3.4",
61
+ "@uniweb/projections": "^0.5.11",
61
62
  "@uniweb/semantic-parser": "^1.4.0",
62
- "@uniweb/theming": "^0.1.15",
63
- "@uniweb/content-reader": "^1.2.4",
64
- "@uniweb/projections": "^0.5.9",
65
- "@uniweb/schemas": "^0.2.13"
63
+ "@uniweb/schemas": "^0.2.13",
64
+ "@uniweb/theming": "^0.1.15"
66
65
  },
67
66
  "optionalDependencies": {
68
- "@uniweb/runtime": "^0.16.0"
67
+ "@uniweb/runtime": "^0.17.1"
69
68
  },
70
69
  "peerDependencies": {
71
70
  "vite": "^5.0.0 || ^6.0.0 || ^7.0.0",
@@ -74,7 +73,7 @@
74
73
  "@tailwindcss/vite": "^4.0.0",
75
74
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
76
75
  "vite-plugin-svgr": "^4.0.0",
77
- "@uniweb/core": "^0.21.0"
76
+ "@uniweb/core": "^0.23.0"
78
77
  },
79
78
  "peerDependenciesMeta": {
80
79
  "vite": {
@@ -24,7 +24,6 @@ export {
24
24
  export {
25
25
  parseFetchConfig,
26
26
  executeFetch,
27
- applyFilter,
28
27
  applySort,
29
28
  applyPostProcessing,
30
29
  mergeDataIntoContent,
@@ -5,7 +5,7 @@
5
5
  *
6
6
  * Exports:
7
7
  * - `components` - Object map of component name -> React component
8
- * - `capabilities` - Custom Layout and props from src/foundation.js (if present)
8
+ * - `capabilities` - Custom Layout and props from src/main.js (if present)
9
9
  * - `meta` - Per-component runtime metadata extracted from meta.js files
10
10
  *
11
11
  * The `meta` export contains only properties needed at runtime:
@@ -81,11 +81,9 @@ async function detectHostShareableImports(srcDir) {
81
81
  /**
82
82
  * Detect foundation config file (for props, vars, etc.)
83
83
  *
84
- * Looks for (in priority order): main.js, main.jsx, foundation.js, foundation.jsx
84
+ * Looks for (in priority order): main.js, main.jsx
85
85
  *
86
- * The canonical name is `main.js` (the foundation package's main authored
87
- * module). `foundation.js` is the legacy name and remains supported so
88
- * existing foundations build without a rename.
86
+ * The foundation's main authored module.
89
87
  *
90
88
  * The file should export:
91
89
  * - props (optional) - Foundation-wide props
@@ -98,8 +96,6 @@ function detectFoundationExports(srcDir) {
98
96
  const candidates = [
99
97
  { path: 'main.js', ext: 'js' },
100
98
  { path: 'main.jsx', ext: 'jsx' },
101
- { path: 'foundation.js', ext: 'js' },
102
- { path: 'foundation.jsx', ext: 'jsx' },
103
99
  ]
104
100
 
105
101
  for (const { path, ext } of candidates) {
@@ -156,7 +152,7 @@ function generateEntrySource(components, options = {}) {
156
152
  }
157
153
 
158
154
  // Foundation capabilities import (for props, vars, etc.)
159
- // Note: Layout/layouts no longer merged from foundation.js — layouts come from src/layouts/ discovery
155
+ // Note: Layout/layouts no longer merged from main.js — layouts come from src/layouts/ discovery
160
156
  if (foundationExports) {
161
157
  lines.push(`import * as _foundationModule from '${foundationExports.path}'`)
162
158
  }
@@ -409,8 +405,6 @@ export function getStructuralWatchPaths(srcDir, options = {}) {
409
405
  'meta.js',
410
406
  'main.js',
411
407
  'main.jsx',
412
- 'foundation.js',
413
- 'foundation.jsx',
414
408
  'styles.css',
415
409
  'index.css'
416
410
  ]
@@ -459,7 +453,7 @@ export function shouldRegenerateForFile(file, srcDir) {
459
453
  return 'meta.js changed'
460
454
  }
461
455
 
462
- // main.js / main.jsx / foundation.js / foundation.jsx at root —
456
+ // main.js / main.jsx at root —
463
457
  // the authored declarations file affects the capabilities import.
464
458
  if (/^(main|foundation)\.(js|jsx)$/.test(rel)) {
465
459
  return 'foundation config changed'
package/src/i18n/index.js CHANGED
@@ -23,7 +23,7 @@ import {
23
23
  translateRecordData,
24
24
  RECORDS_DIR
25
25
  } from './records.js'
26
- import { generateSearchIndex, isSearchEnabled } from '../search/index.js'
26
+ import { generateSearchIndex, isSearchEnabled } from '@uniweb/projections'
27
27
 
28
28
  // Free-form translation support
29
29
  import {
package/src/index.js CHANGED
@@ -8,7 +8,6 @@
8
8
  export {
9
9
  loadComponentMeta,
10
10
  loadFoundationConfig,
11
- loadFoundationMeta, // @deprecated - use loadFoundationConfig
12
11
  discoverComponents,
13
12
  buildSchema,
14
13
  getExposedComponents,
@@ -154,7 +154,6 @@ function isFullSchemaFormat(schema) {
154
154
  * the same schema feeds both audiences.
155
155
  *
156
156
  * Normalizations:
157
- * - `type: 'string'` → `type: 'text'` (legacy alias; warn in dev)
158
157
  *
159
158
  * @param {Object} schema - Rich schema as authored
160
159
  * @returns {Object} - Normalized rich schema
@@ -170,14 +169,7 @@ function normalizeRichSchemaValue(value) {
170
169
  if (!value || typeof value !== 'object') return value
171
170
  const out = {}
172
171
  for (const [key, v] of Object.entries(value)) {
173
- if (key === 'type' && v === 'string') {
174
- if (typeof process !== 'undefined' && process.env?.NODE_ENV !== 'production') {
175
- console.warn(
176
- "[uniweb] form schema field type 'string' is a legacy alias; use 'text' instead."
177
- )
178
- }
179
- out[key] = 'text'
180
- } else if (v && typeof v === 'object') {
172
+ if (v && typeof v === 'object') {
181
173
  out[key] = normalizeRichSchemaValue(v)
182
174
  } else {
183
175
  out[key] = v
package/src/schema.js CHANGED
@@ -21,10 +21,8 @@ import { collectSchemaRefs, buildDataSchemaMap } from './resolve-data-schema.js'
21
21
  // Component meta file name
22
22
  const META_FILE_NAME = 'meta.js'
23
23
 
24
- // Foundation authored-declarations file name. `main.js` is the canonical
25
- // new name; `foundation.js` is the legacy name kept for backward compat.
26
- // Whichever exists at the source root is loaded.
27
- const FOUNDATION_FILE_NAMES = ['main.js', 'foundation.js']
24
+ // Foundation authored-declarations file name.
25
+ const FOUNDATION_FILE_NAMES = ['main.js']
28
26
 
29
27
  // Default paths to scan for section types (relative to srcDir).
30
28
  // Exported so dev-server watchers can derive their watch surface from the same
@@ -394,13 +392,6 @@ function inferFontVarTypes(vars) {
394
392
  return out
395
393
  }
396
394
 
397
- /**
398
- * @deprecated Use loadFoundationConfig instead
399
- */
400
- export async function loadFoundationMeta(srcDir) {
401
- return loadFoundationConfig(srcDir)
402
- }
403
-
404
395
  /**
405
396
  * Check if a filename looks like a PascalCase component (starts with uppercase)
406
397
  */
@@ -14,9 +14,7 @@
14
14
  * - input: Input field mapping
15
15
  * - props: Additional component props (merged with other params)
16
16
  * - fetch: Data fetching configuration (path, url, schema, prerender, merge, transform)
17
- *
18
- * Note: `component` is supported as an alias for `type` (deprecated)
19
- *
17
+ *
20
18
  * Uses @uniweb/content-reader for markdown → ProseMirror conversion
21
19
  * when available, otherwise uses a simplified parser.
22
20
  *
@@ -923,7 +921,7 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
923
921
  console.warn(`[content-collector] ${err.message}`)
924
922
  }
925
923
 
926
- const { type, component, preset, input, props, fetch, data, id: frontmatterId, ...params } = frontMatter
924
+ const { type, preset, input, props, fetch, data, id: frontmatterId, ...params } = frontMatter
927
925
 
928
926
  // Convert markdown to ProseMirror
929
927
  const proseMirrorContent = markdownToProseMirror(markdown)
@@ -961,7 +959,7 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
961
959
  const section = {
962
960
  id,
963
961
  stableId,
964
- type: type || component || null, // frontmatter: type > component (legacy)
962
+ type: type || null,
965
963
  preset,
966
964
  input,
967
965
  params: { ...params, ...props },
@@ -11,7 +11,7 @@
11
11
  * - Local JSON/YAML files
12
12
  * - Remote URLs
13
13
  * - Transform paths to extract nested data
14
- * - Post-processing: limit, sort, filter
14
+ * - Post-processing: limit, sort
15
15
  *
16
16
  * @module @uniweb/build/site/data-fetcher
17
17
  */
@@ -65,68 +65,6 @@ function getNestedValue(obj, path) {
65
65
  return current
66
66
  }
67
67
 
68
- /**
69
- * Parse a filter value from string
70
- *
71
- * @param {string} raw - Raw value string
72
- * @returns {any} Parsed value
73
- */
74
- function parseFilterValue(raw) {
75
- if (raw === 'true') return true
76
- if (raw === 'false') return false
77
- if (raw === 'null') return null
78
- if (/^\d+$/.test(raw)) return parseInt(raw, 10)
79
- if (/^\d+\.\d+$/.test(raw)) return parseFloat(raw)
80
-
81
- // Remove quotes if present
82
- if ((raw.startsWith('"') && raw.endsWith('"')) ||
83
- (raw.startsWith("'") && raw.endsWith("'"))) {
84
- return raw.slice(1, -1)
85
- }
86
-
87
- return raw
88
- }
89
-
90
- /**
91
- * Apply filter expression to array of items
92
- *
93
- * Supported operators: ==, !=, >, <, >=, <=, contains
94
- *
95
- * @param {Array} items - Items to filter
96
- * @param {string} filterExpr - Filter expression (e.g., "published != false")
97
- * @returns {Array} Filtered items
98
- *
99
- * @example
100
- * applyFilter(items, 'published != false')
101
- * applyFilter(items, 'tags contains featured')
102
- */
103
- export function applyFilter(items, filterExpr) {
104
- if (!filterExpr || !Array.isArray(items)) return items
105
-
106
- const match = filterExpr.match(/^(\S+)\s*(==|!=|>=?|<=?|contains)\s*(.+)$/)
107
- if (!match) return items
108
-
109
- const [, field, op, rawValue] = match
110
- const value = parseFilterValue(rawValue.trim())
111
-
112
- return items.filter(item => {
113
- const itemValue = getNestedValue(item, field)
114
- switch (op) {
115
- case '==': return itemValue === value
116
- case '!=': return itemValue !== value
117
- case '>': return itemValue > value
118
- case '<': return itemValue < value
119
- case '>=': return itemValue >= value
120
- case '<=': return itemValue <= value
121
- case 'contains':
122
- return Array.isArray(itemValue)
123
- ? itemValue.includes(value)
124
- : String(itemValue).includes(value)
125
- default: return true
126
- }
127
- })
128
- }
129
-
130
68
  /**
131
69
  * Apply a `sort:` to an array of items — `@uniweb/core`'s ONE evaluator, the
132
70
  * same the runtime's fallback runs, so a query orders identically on the file
@@ -134,7 +72,7 @@ export function applyFilter(items, filterExpr) {
134
72
  *
135
73
  * ⛔ SINGLE-KEY, BY RULING [Diego, 2026-09-04]. This was its own implementation
136
74
  * until then, and it honoured `order asc, title asc` — a multi-key sort the
137
- * records door refuses and the ruling dropped. A comma now THROWS here, at build
75
+ * records service refuses and the ruling dropped. A comma now THROWS here, at build
138
76
  * time, which is where an authoring error on the file lane belongs.
139
77
  *
140
78
  * @param {Array} items - Items to sort
@@ -152,7 +90,7 @@ export function applySort(items, sortExpr) {
152
90
  * The where-object is the query language (see @uniweb/core's
153
91
  * matchWhere). Structured JSON predicate; the one evaluator walks the
154
92
  * object against each record, here at build time and in the runtime
155
- * alike. The same shape crosses to a host's question door unchanged.
93
+ * alike. The same shape crosses to a host's records service unchanged.
156
94
  *
157
95
  * @param {Array} items - Items to filter
158
96
  * @param {object} where - Where-object predicate
@@ -164,25 +102,20 @@ export function applyWhere(items, where) {
164
102
  }
165
103
 
166
104
  /**
167
- * Apply post-processing to fetched data (where, filter, sort, limit)
105
+ * Apply post-processing to fetched data (where, sort, limit)
168
106
  *
169
107
  * Order of operations:
170
- * 1. where (where-object predicate, new) — narrows the record set
171
- * 2. filter (legacy DSL string) — narrows further if both are set; deprecated
172
- * 3. sort
173
- * 4. limit
174
- *
175
- * `where:` and `filter:` may both appear during the deprecation window
176
- * but in practice authors should pick one. Using `filter:` emits a dev
177
- * warning at parse time (see parseFetchConfig).
108
+ * 1. where (where-object predicate) — narrows the record set
109
+ * 2. sort
110
+ * 3. limit
178
111
  *
179
112
  * @param {any} data - Fetched data
180
- * @param {object} config - Fetch config with optional where, filter, sort, limit
113
+ * @param {object} config - Fetch config with optional where, sort, limit
181
114
  * @returns {any} Processed data
182
115
  */
183
116
  export function applyPostProcessing(data, config) {
184
117
  if (!data || !Array.isArray(data)) return data
185
- if (!config.where && !config.filter && !config.sort && !config.limit) return data
118
+ if (!config.where && !config.sort && !config.limit) return data
186
119
 
187
120
  let result = data
188
121
 
@@ -191,11 +124,6 @@ export function applyPostProcessing(data, config) {
191
124
  result = applyWhere(result, config.where)
192
125
  }
193
126
 
194
- // Apply legacy filter expression (deprecated)
195
- if (config.filter) {
196
- result = applyFilter(result, config.filter)
197
- }
198
-
199
127
  // Apply sort
200
128
  if (config.sort) {
201
129
  result = applySort(result, config.sort)
@@ -240,7 +168,7 @@ export function applyPostProcessing(data, config) {
240
168
  // not understand, but we can refuse to pretend it was never there. Reported
241
169
  // once per key name per process so a 200-record build does not print 200 lines.
242
170
  const RECOGNIZED_FETCH_KEYS = {
243
- refine: new Set(['refine', 'detail', 'limit', 'sort', 'where', 'filter']),
171
+ refine: new Set(['refine', 'detail', 'limit', 'sort', 'where']),
244
172
  // ⛔ `schema` IS NOT ON EITHER LIST, and its absence is the point. It was the
245
173
  // binding key until 2026-09-02 and stopped being READ on 2026-09-03 (`e4fe077`,
246
174
  // one name no alias) — but it was left on these lists, which exempted it from
@@ -250,11 +178,11 @@ const RECOGNIZED_FETCH_KEYS = {
250
178
  // "unrecognized" understates a key that used to work.
251
179
  query: new Set([
252
180
  'query', 'as', 'prerender', 'merge', 'transform',
253
- 'where', 'limit', 'sort', 'detailPage', 'filter',
181
+ 'where', 'limit', 'sort', 'detailPage',
254
182
  ]),
255
183
  source: new Set([
256
184
  'path', 'url', 'as', 'prerender', 'merge', 'transform', 'detail',
257
- 'detailPage', 'where', 'limit', 'sort', 'filter',
185
+ 'detailPage', 'where', 'limit', 'sort',
258
186
  ]),
259
187
  }
260
188
 
@@ -377,21 +305,19 @@ export function parseFetchConfig(fetch) {
377
305
  // No URL — merges with the parent fetch config at runtime; only carries
378
306
  // override props.
379
307
  //
380
- // Note on build-vs-runtime scope: this parser passes `sort` and `filter`
308
+ // Note on build-vs-runtime scope: this parser passes `sort`
381
309
  // through on refine configs, but the runtime EntityStore only applies
382
- // `detail`, `limit`, and `order` overrides. `sort` / `filter` on a refine
310
+ // `detail`, `limit`, and `order` overrides. `sort` on a refine
383
311
  // block are currently accepted by the parser but not honored at runtime.
384
312
  // Preserved as-is in this rename commit; revisit separately if needed.
385
313
  if (fetch.refine === true) {
386
314
  warnUnknownFetchKeys(fetch, 'refine')
387
- if (fetch.filter !== undefined) warnFilterDeprecated()
388
315
  return {
389
316
  refine: true,
390
317
  ...(fetch.detail !== undefined ? { detail: fetch.detail } : {}),
391
318
  ...(fetch.limit !== undefined ? { limit: fetch.limit } : {}),
392
319
  ...(fetch.sort !== undefined ? { sort: fetch.sort } : {}),
393
320
  ...(fetch.where !== undefined ? { where: fetch.where } : {}),
394
- ...(fetch.filter !== undefined ? { filter: fetch.filter } : {}),
395
321
  }
396
322
  }
397
323
 
@@ -411,14 +337,13 @@ export function parseFetchConfig(fetch) {
411
337
  // Named-query reference: { query: 'articles', limit: 3 }
412
338
  if (fetch.query) {
413
339
  warnUnknownFetchKeys(fetch, 'query')
414
- if (fetch.filter !== undefined) warnFilterDeprecated()
415
340
  warnSchemaRetired(fetch, fetch.as || fetch.query)
416
341
  return {
417
342
  // ⭐ **`query` IS EMITTED, and that is what makes the two producers agree.**
418
343
  // The sync lane has always emitted it (`uwx/site.js`) and this one did not,
419
344
  // for the same declaration — so `resolveQuerySource` fired on a published
420
345
  // site and never on a `--link`-deployed one. Measured 2026-09-02 against a
421
- // host declaring `config.records`:
346
+ // host offering the `records` service:
422
347
  //
423
348
  // --link endpoint undefined, path /data/articles.json ← the STATIC file
424
349
  // publish endpoint /_api/q/articles ← the live lane
@@ -450,8 +375,6 @@ export function parseFetchConfig(fetch) {
450
375
  // Canonical detail page for a list card's href (page:<stable_id> ref;
451
376
  // resolved to a route template + interpolated per record at runtime).
452
377
  detailPage: fetch.detailPage,
453
- // Legacy post-processing (deprecated, see warning above)
454
- filter: fetch.filter,
455
378
  }
456
379
  }
457
380
 
@@ -469,14 +392,11 @@ export function parseFetchConfig(fetch) {
469
392
  where,
470
393
  limit,
471
394
  sort,
472
- // Legacy post-processing (deprecated)
473
- filter,
474
395
  } = fetch
475
396
 
476
397
  // Must have either path or url
477
398
  if (!path && !url) return null
478
399
 
479
- if (filter !== undefined) warnFilterDeprecated()
480
400
  warnSchemaRetired(fetch, as ?? inferSchemaFromPath(path || url))
481
401
 
482
402
  return {
@@ -496,8 +416,6 @@ export function parseFetchConfig(fetch) {
496
416
  where,
497
417
  limit,
498
418
  sort,
499
- // Legacy post-processing (deprecated)
500
- filter,
501
419
  }
502
420
  }
503
421
 
@@ -541,18 +459,6 @@ export function _resetRetiredSchemaWarnings() {
541
459
  warnedRetiredSchema.clear()
542
460
  }
543
461
 
544
- let filterDeprecationWarned = false
545
- function warnFilterDeprecated() {
546
- if (filterDeprecationWarned) return
547
- filterDeprecationWarned = true
548
- console.warn(
549
- "[uniweb] 'fetch: { filter: ... }' (DSL string) is deprecated; use 'where: { ... }' " +
550
- 'with a where-object. Example: where: { tags: \"featured\" } instead of ' +
551
- "filter: 'tags contains featured'. " +
552
- 'Accepted for one release; will be removed in the next minor.'
553
- )
554
- }
555
-
556
462
  /**
557
463
  * Keys a fetch declaration carries for THE BUILD ONLY, which no runtime reads.
558
464
  *
@@ -716,7 +622,7 @@ export async function executeFetch(config, options = {}) {
716
622
  data = getNestedValue(data, transform)
717
623
  }
718
624
 
719
- // Apply post-processing (filter, sort, limit)
625
+ // Apply post-processing (where, sort, limit)
720
626
  data = applyPostProcessing(data, config)
721
627
 
722
628
  // Ensure we return an array or object, defaulting to empty array
package/src/site/index.js CHANGED
@@ -46,7 +46,6 @@ export { collectSchemalessData, collectSchemalessDataAssets, rewriteSchemalessDa
46
46
  export {
47
47
  parseFetchConfig,
48
48
  executeFetch,
49
- applyFilter,
50
49
  applySort,
51
50
  applyPostProcessing,
52
51
  mergeDataIntoContent
@@ -209,7 +209,7 @@ async function processDevSectionFetches(sections, fetchOptions) {
209
209
  }
210
210
  }
211
211
  }
212
- import { generateSearchIndex, isSearchEnabled, getSearchIndexFilename } from '../search/index.js'
212
+ import { generateSearchIndex, isSearchEnabled, getSearchIndexFilename } from '@uniweb/projections'
213
213
  import { mergeTranslations } from '../i18n/merge.js'
214
214
  import { mountDevApi } from '../dev/api-mount.js'
215
215
 
@@ -105,10 +105,8 @@ function normalizeQueryDecl(name, decl) {
105
105
  * @param {object} [opts]
106
106
  * @param {object} [opts.siteYml] - an already-read site.yml (avoids a re-read)
107
107
  * @returns {Promise<{
108
- * folderSync: boolean, // vestigial — see below; always true
109
108
  * hasQueriesYml: boolean,
110
109
  * declarations: object, // { name: decl } — merged, schema-defaulted
111
- * folders: Array|null, // the folder's virtual org, or null
112
110
  * }>}
113
111
  */
114
112
  export async function resolveQueriesConfig(siteRoot, opts = {}) {
@@ -172,25 +170,20 @@ export async function resolveQueriesConfig(siteRoot, opts = {}) {
172
170
 
173
171
  await deriveDeferredFromSchemas(siteRoot, siteYml, declarations)
174
172
 
175
- // ⛔ BOTH OF THESE ARE VESTIGIAL FOR ONE STEP, and deliberately not deleted here.
173
+ // ⛔ `folderSync` AND `folders` WERE DELETED HERE, 2026-09-06. Both were
174
+ // `collections.yml` survivors kept "for one step" while `records.yml` took
175
+ // over, and both had become literals — `true` and `null` — that no longer
176
+ // decided anything. `folderSync` was `collections.yml::sync`; its one reader
177
+ // guarded on `!folderSync`, a branch that had been unreachable since the
178
+ // literal landed. `folders` was the virtual org, and by the time it was
179
+ // deleted it had **no reader at all** — the comment naming
180
+ // `uwx/sync-package.js` was itself stale.
176
181
  //
177
- // `folderSync` was `collections.yml::sync`. The model DELETES that mechanism
178
- // rather than porting it: "do not sync" becomes "reference nothing in
179
- // `records.yml`" — the actual round trip. Its one reader is
180
- // `uwx/records.js`, and it goes when `records.yml` supplies the real
181
- // control. Until then it must stay TRUE, or nothing syncs at all.
182
- //
183
- // `folders` was `collections.yml::folders`, the virtual org. Its one reader is
184
- // `uwx/sync-package.js`, and `records.yml` replaces it. Null meanwhile is the
185
- // long-standing default (`folder.js::defaultContents` — one branch per query).
186
- //
187
- // ⚠️ Leaving them as literals rather than ripping out their readers keeps this
188
- // step revertible on its own, which is the whole reason the work is ordered.
182
+ // ⚠️ The lesson is the shape, not the keys: a vestige parked as a literal
183
+ // stops failing, so nothing brings anyone back to remove it.
189
184
  return {
190
- folderSync: true,
191
185
  hasQueriesYml,
192
186
  declarations,
193
- folders: null,
194
187
  }
195
188
  }
196
189
 
@@ -55,7 +55,7 @@ import { existsSync } from 'node:fs'
55
55
  import yaml from 'js-yaml'
56
56
  import { parseBibtex } from '@citestyle/bibtex'
57
57
  import { DATA_DIR, fillRoutePattern } from '@uniweb/core'
58
- import { applyWhere, applyFilter, applySort } from './data-fetcher.js'
58
+ import { applyWhere, applySort } from './data-fetcher.js'
59
59
  import { resolveAssetPath, walkContentAssets, isLocalAssetPath } from './assets.js'
60
60
  import { readEntityPool, groupPoolBySchema, ENTITIES_DIR } from './entity-pool.js'
61
61
  import { readRecordsConfig, resolveFolder, FOLDER_MISSING } from './records-config.js'
@@ -715,11 +715,6 @@ async function collectItems(siteDir, config, entitiesDir, basePath) {
715
715
  items = applyWhere(items, config.where)
716
716
  }
717
717
 
718
- // Apply the legacy filter expression (deprecated)
719
- if (config.filter) {
720
- items = applyFilter(items, config.filter)
721
- }
722
-
723
718
  // Apply sort
724
719
  if (config.sort) {
725
720
  items = applySort(items, config.sort)
@@ -249,7 +249,7 @@ export function resolveFolder(entries, pool) {
249
249
  // ⭐ `name` is the handle (the URL segment, sibling-unique); `label` is the
250
250
  // display text. The store renamed the pair on 2026-09-04 — `path_segment` →
251
251
  // `name`, and the old `name` (display) → `label` — so one word means one
252
- // thing from records.yml (`folder:` / `label:`) to the wire to the door's
252
+ // thing from records.yml (`folder:` / `label:`) to the wire to the service's
253
253
  // `$name`.
254
254
  const branch = { kind: 'branch', name: segment }
255
255
  if (entry.label !== undefined && entry.label !== null) branch.label = String(entry.label)
package/src/uwx/folder.js CHANGED
@@ -12,7 +12,7 @@
12
12
  // or `$ref: "<id>"` while brand-new (resolved within this payload).
13
13
  // - a BRANCH is a sub-folder: `{ kind: 'branch', name, label?, $children }`.
14
14
  //
15
- // ⭐ `name` IS THE HANDLE — the URL segment, sibling-unique, the door's `$name` —
15
+ // ⭐ `name` IS THE HANDLE — the URL segment, sibling-unique, the records service's `$name` —
16
16
  // and `label` is the display text, a localized map (`{ en: "Blog" }`). The store
17
17
  // renamed the pair on 2026-09-04 (`path_segment` → `name`; the old display `name`
18
18
  // → `label`); this emitter writes the new shape only and the pull reader
@@ -558,9 +558,6 @@ export async function buildRecordEntities(siteRoot, opts = {}) {
558
558
  const recordsState = recordsCfg.state
559
559
 
560
560
  const colConfig = opts.queriesConfig || (await resolveQueriesConfig(siteRoot))
561
- if (!colConfig.folderSync) {
562
- return { entities: [], index: [], warnings: [], schemaless: [], mappedCount: 0, colConfig, recordsState, folder: null }
563
- }
564
561
  const mapped = syncableQueries(colConfig.declarations)
565
562
  if (mapped.length === 0) return { entities: [], index: [], warnings: [], schemaless: [], mappedCount: 0, colConfig, recordsState, folder: null }
566
563
 
package/src/uwx/site.js CHANGED
@@ -246,7 +246,7 @@ function buildPageData(config, ctx) {
246
246
  // ⭐ BOTH, deliberately, and they are not redundant.
247
247
  //
248
248
  // `query` — the author's named query, unresolved. A consumer that can ask
249
- // a host where records live (`config.records`) resolves it there, which
249
+ // a host where records live (`config.services.records`) resolves it there, which
250
250
  // is the only way a live lane is reachable at all: a resolved path names
251
251
  // one place and closes the question.
252
252
  //
@@ -1,2 +0,0 @@
1
- /** @deprecated Import from `@uniweb/projections` — kept so existing paths resolve. */
2
- export { extractSearchContent, extractSearchContent as default } from '@uniweb/projections/search'
@@ -1,8 +0,0 @@
1
- /** @deprecated Import from `@uniweb/projections` — kept so existing paths resolve. */
2
- export {
3
- generateSearchIndex,
4
- generateSearchIndex as default,
5
- isSearchEnabled,
6
- getSearchConfig,
7
- getSearchIndexFilename
8
- } from '@uniweb/projections/search'
@@ -1,20 +0,0 @@
1
- /**
2
- * Search Index Generation Module
3
- *
4
- * Re-export of `@uniweb/projections/search`. The implementation moved there so
5
- * one copy serves every publisher of a site — the CLI and the app both import
6
- * it, and the backend stores the result rather than generating its own. This
7
- * shim keeps `@uniweb/build/search` and every existing call site working.
8
- *
9
- * @module @uniweb/build/search
10
- */
11
-
12
- export {
13
- extractSearchContent,
14
- extractSearchContent as default,
15
- generateSearchIndex,
16
- isSearchEnabled,
17
- getSearchConfig,
18
- getSearchIndexFilename,
19
- generateRecordSearchIndex
20
- } from '@uniweb/projections/search'
@@ -1,2 +0,0 @@
1
- /** @deprecated Import from `@uniweb/projections` — kept so existing paths resolve. */
2
- export { generateRecordSearchIndex } from '@uniweb/projections/search'