@uniweb/runtime 0.6.23 → 0.6.25

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/runtime",
3
- "version": "0.6.23",
3
+ "version": "0.6.25",
4
4
  "description": "Minimal runtime for loading Uniweb foundations",
5
5
  "type": "module",
6
6
  "exports": {
@@ -35,13 +35,13 @@
35
35
  "node": ">=20.19"
36
36
  },
37
37
  "dependencies": {
38
- "@uniweb/theming": "0.1.3",
39
- "@uniweb/core": "0.5.17"
38
+ "@uniweb/core": "0.5.18",
39
+ "@uniweb/theming": "0.1.3"
40
40
  },
41
41
  "devDependencies": {
42
42
  "@vitejs/plugin-react": "^4.5.2",
43
43
  "vite": "^7.3.1",
44
- "@uniweb/build": "0.8.26"
44
+ "@uniweb/build": "0.8.28"
45
45
  },
46
46
  "peerDependencies": {
47
47
  "react": "^18.0.0 || ^19.0.0",
@@ -122,40 +122,47 @@ export default function PageRenderer() {
122
122
  let page = website?.getPage(location.pathname)
123
123
  if (page && website) website.setActivePage(location.pathname)
124
124
 
125
- // Handle redirect pages navigate to target immediately
126
- const redirectTarget = page?.redirect
127
- useEffect(() => {
128
- if (!redirectTarget) return
129
- if (redirectTarget.startsWith('http')) {
130
- window.location.replace(redirectTarget)
131
- } else {
132
- navigate(redirectTarget, { replace: true })
133
- }
134
- }, [redirectTarget, navigate])
125
+ // ─── Compute navigation targets (before hooks, no early returns) ───
135
126
 
136
- if (redirectTarget) return null
127
+ // Explicit redirect: in page.yml
128
+ const redirectTarget = page?.redirect || null
137
129
 
138
- // Rewrite pages are served by an external site — the host handles routing.
139
- // In SPA mode this shouldn't be reached (host proxies before JS loads),
140
- // but if it is (e.g., dev mode), do a full page reload to let the host handle it.
141
- if (page?.rewrite) {
142
- window.location.reload()
143
- return null
144
- }
130
+ // Auto-redirect for content-less pages (structural containers).
131
+ // Folders with page.yml but no markdown keep their route in the hierarchy;
132
+ // when visited directly, redirect to the first descendant with content.
133
+ const autoRedirectRoute = page && !page.redirect && !page.hasContent()
134
+ ? page.getNavigableRoute()
135
+ : null
136
+ const shouldAutoRedirect = !!(autoRedirectRoute && autoRedirectRoute !== page?.route)
145
137
 
146
138
  // If no page found, try the 404 page (do NOT fall back to activePage/homepage)
147
- const isNotFound = !page
139
+ const isNotFound = !page && !redirectTarget
148
140
  if (isNotFound) {
149
141
  page = website?.getNotFoundPage?.() || null
150
142
  }
151
143
 
152
- // Get head metadata from page (uses Page.getHeadMeta() if available)
144
+ // Head metadata compute for all cases (redirect pages get a fallback)
153
145
  const headMeta = page?.getHeadMeta?.() || {
154
146
  title: isNotFound ? 'Page Not Found' : (page?.title || 'Website'),
155
147
  description: page?.description || ''
156
148
  }
157
149
 
158
- // Manage head meta tags
150
+ // ─── All hooks called unconditionally (React rules of hooks) ───
151
+
152
+ useEffect(() => {
153
+ if (!redirectTarget) return
154
+ if (redirectTarget.startsWith('http')) {
155
+ window.location.replace(redirectTarget)
156
+ } else {
157
+ navigate(redirectTarget, { replace: true })
158
+ }
159
+ }, [redirectTarget, navigate])
160
+
161
+ useEffect(() => {
162
+ if (!shouldAutoRedirect) return
163
+ navigate(autoRedirectRoute, { replace: true })
164
+ }, [shouldAutoRedirect, autoRedirectRoute, navigate])
165
+
159
166
  useHeadMeta(headMeta, { siteName })
160
167
 
161
168
  // For dynamic pages created before collection data was available (hard reload),
@@ -168,6 +175,19 @@ export default function PageRenderer() {
168
175
  return website.dataStore.onUpdate(forceUpdate)
169
176
  }, [isDynamicPending, website])
170
177
 
178
+ // ─── Early returns (after all hooks) ───
179
+
180
+ if (redirectTarget) return null
181
+ if (shouldAutoRedirect) return null
182
+
183
+ // Rewrite pages are served by an external site — the host handles routing.
184
+ // In SPA mode this shouldn't be reached (host proxies before JS loads),
185
+ // but if it is (e.g., dev mode), do a full page reload to let the host handle it.
186
+ if (page?.rewrite) {
187
+ window.location.reload()
188
+ return null
189
+ }
190
+
171
191
  if (!page) {
172
192
  // No page and no 404 page defined - show shared fallback + dev debug info
173
193
  const requestedPath = location.pathname