@uniweb/build 0.68.0 → 0.69.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.68.0",
3
+ "version": "0.69.0",
4
4
  "description": "Build tooling for the Uniweb Component Web Platform",
5
5
  "type": "module",
6
6
  "exports": {
@@ -57,12 +57,12 @@
57
57
  "js-yaml": "^4.1.0",
58
58
  "sharp": "^0.35.3",
59
59
  "yaml": "^2.5.0",
60
- "@uniweb/content-reader": "^1.2.6",
61
- "@uniweb/semantic-parser": "^1.4.1",
62
- "@uniweb/schemas": "^0.8.2",
60
+ "@uniweb/schemas": "^0.8.3",
63
61
  "@uniweb/content-writer": "^0.3.4",
64
- "@uniweb/projections": "^0.7.7",
65
- "@uniweb/theming": "^0.1.15"
62
+ "@uniweb/theming": "^0.1.15",
63
+ "@uniweb/content-reader": "^1.2.7",
64
+ "@uniweb/projections": "^0.7.8",
65
+ "@uniweb/semantic-parser": "^1.4.1"
66
66
  },
67
67
  "optionalDependencies": {
68
68
  "@uniweb/runtime": "^0.27.10"
@@ -74,8 +74,8 @@
74
74
  "@tailwindcss/vite": "^4.0.0",
75
75
  "@vitejs/plugin-react": "^4.0.0 || ^5.0.0",
76
76
  "vite-plugin-svgr": "^4.0.0",
77
- "@uniweb/runtime": "^0.27.10",
78
- "@uniweb/core": "^0.32.2"
77
+ "@uniweb/core": "^0.32.2",
78
+ "@uniweb/runtime": "^0.27.10"
79
79
  },
80
80
  "peerDependenciesMeta": {
81
81
  "vite": {
@@ -10,6 +10,7 @@
10
10
  *
11
11
  * Section frontmatter reserved properties:
12
12
  * - type: Component type (e.g., "Hero", "Features")
13
+ * - hidden: `true` makes the section a draft — left out of a published build, kept in dev
13
14
  * - props: Additional component props (merged with other params)
14
15
  * - fetch: The section's binding — names a query (query, as, where, sort, limit, current,
15
16
  * prerender, detailPage); `query:` is its shorthand
@@ -31,7 +32,7 @@ import yaml from 'js-yaml'
31
32
  import { YAML_OPTIONS } from '../utils/yaml-schema.js'
32
33
  import { collectSectionAssets, mergeAssetCollections, collectConfigAssets } from './assets.js'
33
34
  import { collectSectionIcons, mergeIconCollections, buildIconManifest } from './icons.js'
34
- import { normalizeHideIn, dropUnpublishedPages } from './nav-visibility.js'
35
+ import { normalizeHideIn, dropUnpublishedPages, dropHiddenSections } from './nav-visibility.js'
35
36
  import { parseFetchConfig, toFetchList } from './data-fetcher.js'
36
37
  import { resolveExtensionUrls } from './extension-urls.js'
37
38
  import { buildTheme, extractFoundationVars } from '../theme/index.js'
@@ -1009,7 +1010,17 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
1009
1010
 
1010
1011
  // `query`, `fetch` and `data` are never params: `query:` / `fetch:` declare the
1011
1012
  // section's own data, and a leftover `data:` is refused (`declaredFetch`).
1012
- const { type, preset, input, props, fetch, query, data, id: frontmatterId, ...params } = frontMatter
1013
+ const { type, hidden, preset, input, props, fetch, query, data, id: frontmatterId, ...params } = frontMatter
1014
+ // ⭐ `hidden: true` — a draft section, the page's meaning one level down: left out of a
1015
+ // published build, kept by `uniweb dev`, and synced as the section's own `hidden`
1016
+ // [Diego, 2026-09-28]. ⛔ Until then it was an ordinary param: the component received it
1017
+ // and the section rendered everywhere.
1018
+ if (hidden !== undefined && typeof hidden !== 'boolean') {
1019
+ console.warn(
1020
+ `[content-collector] ${relative(siteRoot, filePath)}: hidden: ${JSON.stringify(hidden)} is not true or false — ` +
1021
+ `the section stays visible.`
1022
+ )
1023
+ }
1013
1024
  // ⛔ `preset:` and `input:` do nothing, so they are dropped with a warning rather than
1014
1025
  // handed to the component as params. Nothing ever read either [2026-09-27]: a preset is
1015
1026
  // a named set of params a section type's `meta.js` offers an editor, and a file writes
@@ -1058,6 +1069,7 @@ async function processMarkdownFile(filePath, id, siteRoot, defaultStableId = nul
1058
1069
  id,
1059
1070
  stableId,
1060
1071
  type: type || null,
1072
+ ...(hidden === true ? { hidden: true } : {}),
1061
1073
  params: { ...params, ...props },
1062
1074
  content: proseMirrorContent,
1063
1075
  fetch: parseFetchConfig(resolvedFetch, relative(siteRoot, filePath), { level: 'section' }),
@@ -2790,10 +2802,11 @@ export async function collectSiteContent(sitePath, options = {}) {
2790
2802
  links: themeLinks
2791
2803
  },
2792
2804
  // Reachability axis: on the published build paths, drop `hidden` pages and
2793
- // their whole subtree (cascade). Dev keeps them so drafts stay previewable.
2794
- pages: dropUnpublished ? dropUnpublishedPages(pages) : pages,
2805
+ // their whole subtree (cascade), and every `hidden` section with its children.
2806
+ // Dev keeps them so drafts stay previewable.
2807
+ pages: dropUnpublished ? dropUnpublishedPages(pages).map(withoutHiddenSections) : pages,
2795
2808
  // Layout area sets: { default: { header: page, footer: page, ... }, marketing: { ... } }
2796
- layouts,
2809
+ layouts: dropUnpublished ? mapLayoutAreas(layouts, withoutHiddenSections) : layouts,
2797
2810
  // ⭐ THE SAME REACHABILITY AXIS, for the 404 slot. `hidden: true` means DRAFT —
2798
2811
  // "excluded from the published site" (`docs/reference/page-configuration.md`);
2799
2812
  // `hideIn: ['*']` is the control for "routed but in no nav".
@@ -2806,7 +2819,7 @@ export async function collectSiteContent(sitePath, options = {}) {
2806
2819
  // shell), and `uniweb dev` keeps the page previewable like any other draft.
2807
2820
  // No cascade to resolve: this slot is root-level only, so its own flag is
2808
2821
  // the only one that can apply.
2809
- notFound: dropUnpublished && notFound?.hidden ? null : notFound,
2822
+ notFound: dropUnpublished ? (notFound?.hidden ? null : withoutHiddenSections(notFound)) : notFound,
2810
2823
  // Versioned scopes: route → { versions, latestId }
2811
2824
  versionedScopes: versionedScopesObj,
2812
2825
  assets: assetCollection.assets,
@@ -2817,6 +2830,25 @@ export async function collectSiteContent(sitePath, options = {}) {
2817
2830
  }
2818
2831
  }
2819
2832
 
2833
+ // A page (or a layout area) without its hidden sections — `dropHiddenSections`.
2834
+ function withoutHiddenSections(page) {
2835
+ if (!page || !Array.isArray(page.sections)) return page
2836
+ return { ...page, sections: dropHiddenSections(page.sections) }
2837
+ }
2838
+
2839
+ // Apply `fn` to every area of every layout: `{ default: { header: page, … }, … }`.
2840
+ function mapLayoutAreas(layouts, fn) {
2841
+ if (!layouts || typeof layouts !== 'object') return layouts
2842
+ return Object.fromEntries(
2843
+ Object.entries(layouts).map(([name, areas]) => [
2844
+ name,
2845
+ areas && typeof areas === 'object'
2846
+ ? Object.fromEntries(Object.entries(areas).map(([area, page]) => [area, fn(page)]))
2847
+ : areas,
2848
+ ])
2849
+ )
2850
+ }
2851
+
2820
2852
  // Shared pure / IO helpers. Exported for testing AND reused by
2821
2853
  // @uniweb/build/uwx, the .uwx exporter, which re-walks the source rather
2822
2854
  // than the flattened collector output but reuses these primitives so
@@ -1,6 +1,7 @@
1
1
  // Page-visibility helpers — the two orthogonal axes a page carries:
2
2
  // • Reachability — `hidden: bool`. `true` = excluded from the PUBLISHED output
3
- // entirely (see `dropUnpublishedPages`). Still previewable in `uniweb dev`.
3
+ // entirely (see `dropUnpublishedPages`, and `dropHiddenSections` for a section).
4
+ // Still previewable in `uniweb dev`.
4
5
  // • Nav placement — `hideIn: string[]`. Which nav areas suppress the page while
5
6
  // it IS routed (see `normalizeHideIn`). `['*']` = suppressed from every area.
6
7
 
@@ -67,3 +68,23 @@ export function dropUnpublishedPages(pages) {
67
68
  }
68
69
  return pages.filter((p) => !isUnpublished(p))
69
70
  }
71
+
72
+ /**
73
+ * Drop hidden sections — the same reachability axis, one level down. A section with
74
+ * `hidden: true` in its frontmatter is a draft: left out of the PUBLISHED output together
75
+ * with its child sections, and kept by `uniweb dev` so it stays previewable. Applied where
76
+ * `dropUnpublishedPages` is, to a page's sections and a layout area's alike.
77
+ *
78
+ * @param {Array<object>} sections - collected sections (each may hold `subsections`)
79
+ * @returns {Array<object>} the sections without the hidden ones, at every depth
80
+ */
81
+ export function dropHiddenSections(sections) {
82
+ if (!Array.isArray(sections)) return sections
83
+ return sections
84
+ .filter((section) => section?.hidden !== true)
85
+ .map((section) =>
86
+ Array.isArray(section.subsections) && section.subsections.length > 0
87
+ ? { ...section, subsections: dropHiddenSections(section.subsections) }
88
+ : section
89
+ )
90
+ }
@@ -41,7 +41,8 @@ export const DEFAULT_RESERVED_FRONTMATTER = new Set([
41
41
  'fetch',
42
42
  'data',
43
43
  'nest',
44
- 'hidden',
44
+ // ⛔ Not `hidden`, which was here from the start (2026-05-30) with no reason recorded: an
45
+ // editor toggles it, so a pull must carry the store's value into the file (2026-09-28).
45
46
  ])
46
47
 
47
48
  // js-yaml dump options shared by every config write, so output is byte-stable.
@@ -444,7 +444,7 @@ function reinlineInsets(content, insets) {
444
444
  export function sectionRecordToFile({ filePath, record, sourceLocale = LOCALIZED_FIELD_ASSUMPTION.defaultSourceLocale, collector, freeformRelPath, freeformCandidates = null, writeId = true, context = null }) {
445
445
  // ⛔ A stored `preset` or `input` is not written back: neither has an effect, and the build
446
446
  // would only warn about it (2026-09-27).
447
- const { type, stable_id, params, content, insets, fetch, background, theme_override } = record || {}
447
+ const { type, stable_id, hidden, params, content, insets, fetch, background, theme_override } = record || {}
448
448
 
449
449
  // A localized `content` field unwraps to the source-locale doc for the body; its
450
450
  // target-locale structural maps are captured into the locales/ collector, and any
@@ -452,9 +452,18 @@ export function sectionRecordToFile({ filePath, record, sourceLocale = LOCALIZED
452
452
  // locales/freeform/. A bare doc (source-only / pre-localization) passes through.
453
453
  const sourceContent = unwrapLocalizedContent(content, sourceLocale, collector, freeformRelPath, freeformCandidates, context)
454
454
 
455
+ const { hidden: paramHidden, ...paramsRest } = params && typeof params === 'object' ? params : {}
455
456
  const frontmatter = {}
456
457
  if (type !== undefined) frontmatter.type = type
457
- if (params && typeof params === 'object') Object.assign(frontmatter, params)
458
+ // ⭐ `hidden:` says what the store says: written for a hidden section, and removed from the
459
+ // file of a visible one — an editor toggles it [2026-09-28]. ⛔ Until then a pull dropped it,
460
+ // and the next push un-hid the section. A push before that day sent a file's `hidden:` inside
461
+ // `params`, so one stored there counts when the field is absent.
462
+ frontmatter.hidden = (typeof hidden === 'boolean' ? hidden : paramHidden === true) ? true : undefined
463
+ Object.assign(frontmatter, paramsRest)
464
+ // ⚠️ A stored `background` / `theme_override` field wins over the same key in `params` while
465
+ // one is stored: an editor may still write the fields rather than `params` (2026-09-28;
466
+ // uwx-format.md § A page section's fields, Order).
458
467
  if (background !== undefined) frontmatter.background = background
459
468
  if (theme_override !== undefined) frontmatter.theme = theme_override
460
469
  // Invert the build's resolution rather than copy it — see fetch-shapes.js. A new
package/src/uwx/site.js CHANGED
@@ -142,9 +142,9 @@ function stripCredentials(block, label) {
142
142
  }
143
143
 
144
144
  // page_sections and layout_sections share this content shape.
145
- // processMarkdownFile takes only type/props/fetch/query/data/id out of the
145
+ // processMarkdownFile takes only type/hidden/props/fetch/query/data/id out of the
146
146
  // frontmatter (and drops preset/input), so `background:` and `theme:` stay
147
- // inside section.params — lift them into the entity type's dedicated fields.
147
+ // inside section.params — and ride there on the wire too (`mapSectionData`).
148
148
  // The `type` a push sends for a section whose file names none — the Model requires one. A pull does
149
149
  // not write it back into a file that names none (`sectionRecordToFile`): absent, the foundation's
150
150
  // `defaultSection` renders it, which is not always `Content`.
@@ -302,19 +302,20 @@ function localizePageMeta(page, route, sourceLocale, translations) {
302
302
 
303
303
  function mapSectionData(section) {
304
304
  const params = { ...section.params }
305
- const background = params.background
306
- const themeOverride = params.theme
307
- delete params.background
308
- delete params.theme
309
-
310
305
  const data = { type: section.type || FILLED_SECTION_TYPE } // entity type requires `type`
311
306
  setIf(data, 'stable_id', section.stableId ?? undefined)
307
+ // ⭐ A draft section — the store's own `hidden`, which an editor also writes [2026-09-28].
308
+ if (section.hidden === true) data.hidden = true
312
309
  // No `preset` or `input`: nothing read either, and the collector drops them (2026-09-27).
310
+ // ⭐ `background` and `theme` stay IN `params` [Diego, 2026-09-28] — where the runtime reads
311
+ // them, and where a section stores them from now on. ⚠️ Also sent as the two fields they had
312
+ // until today, until every reader of a stored section reads `params`; a pull prefers a
313
+ // stored field meanwhile (`sectionRecordToFile`). uwx-format.md § A page section's fields.
313
314
  if (Object.keys(params).length > 0) data.params = params
314
315
  data.content = section.content // leaf insets in it as written — no `insets` field since 2026-09-27
315
316
  setIf(data, 'fetch', section.fetch ?? undefined)
316
- setIf(data, 'background', background)
317
- setIf(data, 'theme_override', themeOverride)
317
+ setIf(data, 'background', params.background)
318
+ setIf(data, 'theme_override', params.theme)
318
319
  return data
319
320
  }
320
321