@voltro/web 0.8.0 → 0.9.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/CHANGELOG.md CHANGED
@@ -39,6 +39,25 @@ _Changes staged for the next release accumulate here (rolled up from
39
39
 
40
40
  ---
41
41
 
42
+ ## [0.9.0] — 2026-07-21
43
+
44
+ ### ⚠ BREAKING
45
+
46
+ - **@voltro/web, @voltro/cli** — A `renderMode: 'spa'` page whose route has an SSR layout chain now renders that LAYOUT on the server — an SSR shell — instead of rendering nothing server-side. This decouples layout SSR from page render mode: the layout (nav shell, sidebar, auth gate) is server-rendered for an instant first paint and SEO, while the page itself stays client-only. Concretely, on both `voltro dev` and `voltro start`, a spa page under a layout used to ship the empty client shell (`<div id="root"></div>`) and mount the whole tree — layout included — in the browser. Now the server renders the layout chain around an empty page slot (`<div data-voltro-page-slot>`), runs the LAYOUT loaders, and inlines their data + a `pageClientOnly` flag; the browser hydrates that shell and mounts the client-only page into the slot after hydration. The page's OWN loader still runs in the browser, exactly as before. A spa page with NO layout is unchanged (still a pure client mount); `static`/`ssr`/`isr` pages are unchanged. Why this is BREAKING: a layout component AND its `loader` now execute during the server render for spa routes. Layouts shared with any `static`/`ssr`/`isr` page already ran server-side (and `static` is the default), so they are unaffected — the only newly-server-rendered layout is one whose ENTIRE page subtree is `'spa'`. Such a layout must be SSR-safe: no unguarded `window`/`document` at render time or in its `loader`. The `voltro update` codemod (`0.9.0/01`) prints this review step, and only for projects that actually have both a spa page and a layout. Out of scope this release (follow-ups): build-time prerender of the spa layout shell (`voltro build` still skips spa pages, so `voltro start` renders the shell on demand rather than from a prerendered file), `defer()`/streaming inside a spa-shell layout (the shell is buffered), and Fast-Refresh coverage of the new shell path.
47
+
48
+ ### Added
49
+
50
+ - **@voltro/protocol** — `jwtBearerStrategy` (and `extractBearerOrCookie`) accept an optional `cookieToToken` hook (`CookieTokenExtractor`) that transforms the request's cookie transport into the JWT to verify. It receives a `getCookie(name)` accessor (so a strategy can read sibling / chunked cookies) plus the configured cookie name. Applied to the cookie path only — the Bearer header stays a raw token — and defaulting to a verbatim read, so the raw-JWT cookie path for WorkOS / Kinde / Clerk / Auth0 / OIDC is byte-identical. `supabaseStrategy` supplies one to unwrap the `@supabase/ssr` session envelope. Additive: existing `jwtBearerStrategy` configs and 2-arg `extractBearerOrCookie` calls are unaffected.
51
+ - **@voltro/cli** — `voltro doctor` now flags pages that are safe `renderMode:'spa'` candidates. A page is listed when ALL hold: it is a page file (not `layout`/`loading`/`error`/`not-found`), it exports no `loader`, its `renderMode` is `'ssr'` or unset/default (not already `'spa'`/`'static'`/`'isr'`), AND a `layout.tsx` sits somewhere in its directory chain (root, an ancestor, or the page's own dir). That last condition is load-bearing: only with a layout does switching to `'spa'` keep a server-rendered shell (the layout SSRs while the page body goes client-only) — a page with no layout would, as `'spa'`, ship no server HTML at all, so it is never flagged. The hint is ADVISORY and names the tradeoff: `renderMode:'spa'` skips the page's per-page SSR compile while its layout shell still renders server-side — adopt it for internal/authenticated pages whose body needs no SSR; keep `'ssr'` when the page content needs SEO or server first-paint. We deliberately ship NO codemod to auto-flip pages, because dropping a page body's server render is a per-page product decision, not a mechanically-safe transform. The scan reuses the framework's own page discovery (`walkPagesTree`), so it can't drift from what dev/build classify as a page. The full list is retrievable via `voltro doctor --json` (a new `spaCandidates` array); the human view caps at 10 with a "+N more" pointer.
52
+
53
+ ### Fixed
54
+
55
+ - **@voltro/cli** — `voltro dev` no longer OOM-kills itself when many `renderMode:'ssr'` pages compile at once. The dev SSR renderer compiles each page (and every layout in its chain) on demand via Vite's `ssrLoadModule`, and that call was unbounded: two browser tabs on the same cold route, or a health-check sweep hitting hundreds of distinct routes, each started its OWN esbuild module tree with no dedupe and no concurrency cap, so the transient heaps added up and the process was killed (a downstream app with ~224 SSR pages reached >14 GB in seconds; `--max-old-space-size=8192` died after ~5 concurrent cold pages). The fix is a cold-compile gate (`coldCompileGate.ts`) around every `ssrLoadModule` in the dev SSR handler — the page, each layout/error/loading segment, and the shared `@voltro/web/ssr` helpers, so a sweep can't fan out on layouts either. It does two things: (1) DEDUPES concurrent requests for the same module — N tabs on one cold route trigger ONE compile they all await; (2) BOUNDS how many DISTINCT cold compiles run at once (default 4). A WARM module (already in Vite's graph) bypasses the gate entirely, so a hot app stays fully concurrent — the gate only tames the cold stampede, it never serializes warm serving. Verified: a concurrent sweep of 100 distinct cold routes runs exactly 4 concurrent esbuild trees under the default instead of 100, and 8 concurrent requests to one cold route compile the page once. The bound is overridable with `VOLTRO_DEV_SSR_COMPILE_CONCURRENCY` — drop it to `1`/`2` on a low-memory box, raise it on a beefy one. Default 4 balances memory against cold-sweep throughput (a lower value is safer but serializes first-paint of freshly-hit routes). Production `voltro start` is unaffected: it renders from a precompiled bundle and refuses to boot without one, so it never compiles on demand. The dev-like `voltro start` middleware FALLBACK (used only when `NODE_ENV!=='production'` and no bundle exists) shares the same `ssrLoadModule` path and now goes through the same gate. Covered by `coldCompileGate.test.ts` (dedupe, bound, warm bypass, failure-clears-and-retries, env parsing); the heavy end-to-end reproduction is a scratchpad (`scripts/measure-ssr-compile-oom.mjs`), not a CI gate.
56
+ - **@voltro/cli** — `voltro update` (and `voltro update --codemods-only`) no longer aborts with `ELOOP: too many symbolic links` when the app tree contains a circular symlink. The codemod file scan built its ts-morph `Project` with `addSourceFilesAtPaths([...globs])`, whose underlying glob FOLLOWS symbolic links and applies the `!**/node_modules/**` negations only to the RESULTS — so a self-referential symlink anywhere under the app root (pnpm's package layout inside `node_modules`, or any stray symlinked scratch dir) made the walk descend forever and throw before any negation could apply. The scan now enumerates source files itself with `followSymbolicLinks: false` and prunes heavy directories (`node_modules`, `.git`, `dist`, `build`, `.framework`, `.turbo`, `.next`, `.cache`, `.output`, `.voltro-*`, …) at the traversal level rather than by post-filtering — the crawler never descends into them, and no symlink is followed, so a cycle put there by anything is harmless. App source a codemod rewrites is always real files on disk, so files reachable only through a symlink are deliberately not scanned (and never rewritten). A codemod's explicit `scope` still narrows the file set exactly as before.
57
+ - **@voltro/plugin-auth-supabase** — `supabaseStrategy({ cookieName: 'sb-<ref>-auth-token' })` now reads `@supabase/ssr` session cookies. Those cookies do not hold a raw JWT — the SDK stores the GoTrue session as a JSON envelope, optionally `base64-`-encoded and split across `sb-<ref>-auth-token.0`, `.1`, … chunk cookies. The strategy previously handed that envelope straight to the JWT verifier, so every cookie-mode request failed with `malformed jwt` and fell back to anonymous (including `ctx.query` from a `type:'web'` loader, which forwards the browser cookie to the api). The strategy now unwraps the envelope — URL-decode, strip the `base64-` prefix and base64url-decode when present, concatenate chunks in order, then lift the inner `access_token` — before verification. The `Authorization: Bearer` path is unchanged (always a raw token). Hostile / malformed / oversized cookies resolve to anonymous (skip), never throw. Auth strategies belong on the `type:'api'` app, not the web app — the api verifies the forwarded cookie.
58
+
59
+ ---
60
+
42
61
  ## [0.8.0] — 2026-07-20
43
62
 
44
63
  ### ⚠ BREAKING
package/dist/index.d.ts CHANGED
@@ -615,6 +615,8 @@ export declare class NotFoundError extends Error {
615
615
  constructor(detail?: string);
616
616
  }
617
617
 
618
+ export declare const PAGE_SLOT_ATTR = "data-voltro-page-slot";
619
+
618
620
  export declare interface PageDescriptor<TLoaderData = unknown> {
619
621
  /** URL pattern, e.g. `/`, `/about`, `/users/[id]`, `/docs/[...slug]`. */
620
622
  readonly pattern: string;
@@ -705,6 +707,11 @@ export declare const pageRoute: (pattern: string, mod: Record<string, unknown>,
705
707
  readonly chain?: ReadonlyArray<RouteSegment>;
706
708
  }) => PageDescriptor;
707
709
 
710
+ /** The empty page slot the SSR-layout-shell path renders at the leaf, on BOTH
711
+ * the server (`renderPageToHtml` with `pageSlot`) and the client Router's
712
+ * first render — identical markup, so hydration matches. */
713
+ export declare const PageSlot: () => ReactNode;
714
+
708
715
  export declare const parseCookieHeader: (raw: string | undefined) => Record<string, string>;
709
716
 
710
717
  /** A caller may override any subtree, not the whole object — deep-partial per
package/dist/index.js CHANGED
@@ -2,28 +2,28 @@ import { t as e } from "./globalContext-d4A-ugDg.js";
2
2
  import { AppClientsContext as t, useAppClient as n } from "./hooks.js";
3
3
  import { a as r, c as i, i as a, l as o, o as s, r as c, s as l, u } from "./frameworkBoot-C_d7E8Fj.js";
4
4
  import { a as d, c as f, i as p, l as m, o as h, s as g } from "./defaultFallbacks-CGs2z5qv.js";
5
- import { i as _, n as ee, r as te, t as ne } from "./mount-D3k8Ozdm.js";
6
- import { c as re, d as ie, h as ae, l as oe, p as se, u as ce } from "./routerState-ga64vk2B.js";
7
- import { A as le, B as ue, C as de, D as fe, E as pe, F as me, I as v, L as y, M as b, N as x, O as S, P as C, R as w, S as T, T as E, V as D, _ as O, a as k, b as A, c as j, d as M, f as N, g as P, h as F, i as I, j as L, k as R, l as z, m as he, n as ge, o as _e, p as ve, r as ye, s as be, t as xe, u as Se, v as Ce, w as we, x as Te, y as Ee, z as De } from "./serverContext-BW0GF8fv.js";
8
- import { Component as Oe, Suspense as ke, createElement as B, use as Ae, useCallback as je, useContext as Me, useSyncExternalStore as Ne } from "react";
5
+ import { i as _, n as ee, r as te, t as ne } from "./mount-Gsn3ouYx.js";
6
+ import { c as re, d as ie, h as ae, l as oe, p as se, u as ce } from "./routerState-DpUqogK8.js";
7
+ import { A as le, B as ue, C as de, D as fe, E as pe, F as me, H as v, I as y, L as b, M as x, N as S, O as C, P as w, R as T, S as E, T as D, U as O, V as k, _ as A, a as j, b as M, c as N, d as P, f as F, g as I, h as L, i as R, j as z, k as he, l as ge, m as _e, n as ve, o as ye, p as be, r as xe, s as Se, t as Ce, u as we, v as Te, w as Ee, x as De, y as Oe, z as ke } from "./serverContext-COzippNt.js";
8
+ import { Component as Ae, Suspense as je, createElement as B, use as Me, useCallback as Ne, useContext as Pe, useSyncExternalStore as Fe } from "react";
9
9
  import { Fragment as V, jsx as H, jsxs as U } from "react/jsx-runtime";
10
10
  export * from "@voltro/client";
11
11
  export * from "@voltro/ui";
12
12
  //#region src/await.tsx
13
- var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PURE__ */ H(ke, {
13
+ var Ie = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PURE__ */ H(je, {
14
14
  fallback: t,
15
- children: /* @__PURE__ */ H(Ie, {
15
+ children: /* @__PURE__ */ H(Re, {
16
16
  errorFallback: r,
17
- children: /* @__PURE__ */ H(Fe, {
17
+ children: /* @__PURE__ */ H(Le, {
18
18
  value: e,
19
19
  errorFallback: r ?? null,
20
20
  children: n
21
21
  })
22
22
  })
23
- }), W = ({ body: e }) => /* @__PURE__ */ H("script", { dangerouslySetInnerHTML: { __html: e } }), Fe = ({ value: e, errorFallback: t, children: n }) => {
24
- let r = Ae(e), i = ce(e), a = ae(r);
23
+ }), W = ({ body: e }) => /* @__PURE__ */ H("script", { dangerouslySetInnerHTML: { __html: e } }), Le = ({ value: e, errorFallback: t, children: n }) => {
24
+ let r = Me(e), i = ce(e), a = ae(r);
25
25
  return a === void 0 ? /* @__PURE__ */ U(V, { children: [i === void 0 ? null : /* @__PURE__ */ H(W, { body: ie(i, r) }), n(r)] }) : /* @__PURE__ */ U(V, { children: [i === void 0 ? null : /* @__PURE__ */ H(W, { body: oe(i, a) }), t] });
26
- }, Ie = class extends Oe {
26
+ }, Re = class extends Ae {
27
27
  state = { error: void 0 };
28
28
  static getDerivedStateFromError(e) {
29
29
  return { error: e };
@@ -31,7 +31,7 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
31
31
  render() {
32
32
  return this.state.error === void 0 ? this.props.children : this.props.errorFallback ?? null;
33
33
  }
34
- }, G = ({ src: e }) => e, K = e("imageConfig", { loader: G }), Le = ({ loader: e, children: t }) => B(K.Provider, { value: { loader: e } }, t), q = [
34
+ }, G = ({ src: e }) => e, K = e("imageConfig", { loader: G }), ze = ({ loader: e, children: t }) => B(K.Provider, { value: { loader: e } }, t), q = [
35
35
  640,
36
36
  750,
37
37
  828,
@@ -40,7 +40,7 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
40
40
  1920,
41
41
  2048,
42
42
  3840
43
- ], Re = (e, t, n, r) => {
43
+ ], Be = (e, t, n, r) => {
44
44
  let i = n !== void 0 && !r ? Array.from(new Set(q.filter((e) => e <= n * 2).concat(n))).sort((e, t) => e - t) : [...q];
45
45
  return {
46
46
  srcSet: i.map((n) => `${e({
@@ -52,10 +52,10 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
52
52
  width: i[i.length - 1]
53
53
  })
54
54
  };
55
- }, ze = ({ src: e, alt: t, width: n, height: r, fill: i = !1, sizes: a, priority: o = !1, loader: s, placeholder: c = "empty", blurDataURL: l, style: u, ...d }) => {
56
- let f = Me(K).loader, p = s ?? f;
55
+ }, Ve = ({ src: e, alt: t, width: n, height: r, fill: i = !1, sizes: a, priority: o = !1, loader: s, placeholder: c = "empty", blurDataURL: l, style: u, ...d }) => {
56
+ let f = Pe(K).loader, p = s ?? f;
57
57
  !i && (n === void 0 || r === void 0) && typeof console < "u" && console.warn(`<Image src="${e}"> needs both width and height (or fill) to reserve layout space and avoid CLS.`);
58
- let { srcSet: m, fallback: h } = Re(p, e, n, i), g = c === "blur" && l ? {
58
+ let { srcSet: m, fallback: h } = Be(p, e, n, i), g = c === "blur" && l ? {
59
59
  backgroundImage: `url(${l})`,
60
60
  backgroundSize: "cover",
61
61
  backgroundPosition: "center"
@@ -85,10 +85,10 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
85
85
  },
86
86
  ...d
87
87
  });
88
- }, Be = ({ to: e, children: t, className: n }) => B("a", {
88
+ }, He = ({ to: e, children: t, className: n }) => B("a", {
89
89
  href: e,
90
90
  className: n
91
- }, t), Ve = {
91
+ }, t), Ue = {
92
92
  position: "sticky",
93
93
  top: 0,
94
94
  zIndex: 10,
@@ -99,13 +99,13 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
99
99
  padding: "1rem 1.5rem",
100
100
  borderBottom: "1px solid var(--voltro-blog-border, rgba(127,127,127,0.2))",
101
101
  backdropFilter: "blur(8px)"
102
- }, He = {
102
+ }, We = {
103
103
  maxWidth: "48rem",
104
104
  margin: "0 auto",
105
105
  padding: "2rem 1.5rem",
106
106
  width: "100%"
107
- }, Ue = (e) => {
108
- let { brand: t, topNav: n, children: r, pathname: i, footer: a, breadcrumbs: o, headerRight: s } = e, c = e.Link ?? Be, l = B("nav", {
107
+ }, Ge = (e) => {
108
+ let { brand: t, topNav: n, children: r, pathname: i, footer: a, breadcrumbs: o, headerRight: s } = e, c = e.Link ?? He, l = B("nav", {
109
109
  className: "voltro-blog-nav",
110
110
  style: {
111
111
  display: "flex",
@@ -122,7 +122,7 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
122
122
  });
123
123
  })), u = B("header", {
124
124
  className: "voltro-blog-header",
125
- style: Ve
125
+ style: Ue
126
126
  }, B("div", { className: "voltro-blog-brand" }, t), s ? B("div", { style: {
127
127
  display: "flex",
128
128
  gap: "1rem",
@@ -150,7 +150,7 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
150
150
  })) : null;
151
151
  return B("div", { className: "voltro-blog-layout" }, u, B("main", {
152
152
  className: "voltro-blog-main",
153
- style: He
153
+ style: We
154
154
  }, d, r), a ? B("footer", {
155
155
  className: "voltro-blog-footer",
156
156
  style: {
@@ -160,7 +160,7 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
160
160
  borderTop: "1px solid var(--voltro-blog-border, rgba(127,127,127,0.2))"
161
161
  }
162
162
  }, a) : null);
163
- }, J = "voltro:theme", We = 31536e3, Ge = () => {
163
+ }, J = "voltro:theme", Y = 31536e3, Ke = () => {
164
164
  if (typeof document > "u") return "system";
165
165
  try {
166
166
  let e = document.cookie.match(/(?:^|;\s*)voltro:theme=([^;]*)/), t = e?.[1] === void 0 ? "" : decodeURIComponent(e[1]);
@@ -168,17 +168,17 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
168
168
  } catch {
169
169
  return "system";
170
170
  }
171
- }, Y = () => {
171
+ }, qe = () => {
172
172
  if (typeof window > "u" || !window.matchMedia) return !1;
173
173
  try {
174
174
  return window.matchMedia("(prefers-color-scheme: dark)").matches;
175
175
  } catch {
176
176
  return !1;
177
177
  }
178
- }, X = (e) => e === "system" ? Y() ? "dark" : "light" : e, Z = 0, Q = /* @__PURE__ */ new Set(), $ = () => {
178
+ }, X = (e) => e === "system" ? qe() ? "dark" : "light" : e, Z = 0, Q = /* @__PURE__ */ new Set(), $ = () => {
179
179
  Z++;
180
180
  for (let e of Q) e();
181
- }, Ke = (e) => {
181
+ }, Je = (e) => {
182
182
  Q.add(e);
183
183
  let t, n = () => $();
184
184
  if (typeof window < "u" && window.matchMedia) try {
@@ -190,21 +190,21 @@ var Pe = ({ value: e, fallback: t, children: n, errorFallback: r }) => /* @__PUR
190
190
  t?.removeEventListener("change", n);
191
191
  } catch {}
192
192
  };
193
- }, qe = () => {
194
- let e = Ge();
193
+ }, Ye = () => {
194
+ let e = Ke();
195
195
  return `${Z}:${e}:${X(e)}`;
196
- }, Je = () => "0:system:light", Ye = () => {
197
- let [, e, t] = Ne(Ke, qe, Je).split(":");
196
+ }, Xe = () => "0:system:light", Ze = () => {
197
+ let [, e, t] = Fe(Je, Ye, Xe).split(":");
198
198
  return {
199
199
  theme: e,
200
200
  resolvedTheme: t,
201
- setTheme: je((e) => {
201
+ setTheme: Ne((e) => {
202
202
  if (typeof document < "u") try {
203
- document.cookie = e === "system" ? `${J}=; path=/; max-age=0; SameSite=Lax` : `${J}=${e}; path=/; max-age=${We}; SameSite=Lax`, document.documentElement.classList.toggle("dark", X(e) === "dark");
203
+ document.cookie = e === "system" ? `${J}=; path=/; max-age=0; SameSite=Lax` : `${J}=${e}; path=/; max-age=${Y}; SameSite=Lax`, document.documentElement.classList.toggle("dark", X(e) === "dark");
204
204
  } catch {}
205
205
  $();
206
206
  }, [])
207
207
  };
208
- }, Xe = "framework-web";
208
+ }, Qe = "framework-web";
209
209
  //#endregion
210
- export { t as AppClientsContext, Pe as Await, Ue as BlogLayout, p as FallbackStringsProvider, ze as Image, Le as ImageConfigProvider, _e as Link, be as LoaderCache, j as LoaderDataContext, i as NavigationIndicator, x as NotFoundError, z as PlainLink, o as ReconnectContext, C as RedirectError, Se as Router, M as RouterContext, xe as ServerRequestContext, ge as ServerRequestProvider, J as THEME_COOKIE, Xe as WEB_NAME, N as compileRoute, d as defaultFallbackStrings, re as defer, ve as externalUrl, he as findNotFound, c as formatPrintf, ee as getIslandComponent, De as getRouteSnapshot, g as getStatuses, te as hydrateIslandsOnPage, a as installBrowserConsoleBridge, r as installServerLogRelay, se as isDeferredLoaderResult, me as isNotFound, v as isRedirect, _ as island, F as lazyPageRoute, P as loaderCacheKey, O as matchRoute, ne as mount, y as notFound, Ce as pageRoute, ye as parseCookieHeader, G as passthroughImageLoader, Ee as preloadRouteModule, f as pushStatus, w as redirect, s as registerClientTrace, A as resolveAnchorNavigation, Te as resolveMeta, T as segmentLoaderKey, ue as setRouteSnapshot, de as sortRoutesByPriority, D as subscribeRouteSnapshot, m as subscribeStatuses, l as subscribeTraceErrors, n as useAppClient, we as useBlocker, h as useFallbackStrings, E as useLoaderData, pe as useLocation, fe as useNavigate, S as useParams, R as usePrefetch, u as useReconnect, I as useSearchParams, k as useServerRequest, le as useSetSearchParams, Ye as useTheme, L as withHash, b as withQuery };
210
+ export { t as AppClientsContext, Ie as Await, Ge as BlogLayout, p as FallbackStringsProvider, Ve as Image, ze as ImageConfigProvider, ye as Link, Se as LoaderCache, N as LoaderDataContext, i as NavigationIndicator, me as NotFoundError, ge as PAGE_SLOT_ATTR, we as PageSlot, P as PlainLink, o as ReconnectContext, y as RedirectError, F as Router, be as RouterContext, Ce as ServerRequestContext, ve as ServerRequestProvider, J as THEME_COOKIE, Qe as WEB_NAME, _e as compileRoute, d as defaultFallbackStrings, re as defer, L as externalUrl, I as findNotFound, c as formatPrintf, ee as getIslandComponent, k as getRouteSnapshot, g as getStatuses, te as hydrateIslandsOnPage, a as installBrowserConsoleBridge, r as installServerLogRelay, se as isDeferredLoaderResult, b as isNotFound, T as isRedirect, _ as island, A as lazyPageRoute, Te as loaderCacheKey, Oe as matchRoute, ne as mount, ke as notFound, M as pageRoute, xe as parseCookieHeader, G as passthroughImageLoader, De as preloadRouteModule, f as pushStatus, ue as redirect, s as registerClientTrace, E as resolveAnchorNavigation, de as resolveMeta, Ee as segmentLoaderKey, v as setRouteSnapshot, D as sortRoutesByPriority, O as subscribeRouteSnapshot, m as subscribeStatuses, l as subscribeTraceErrors, n as useAppClient, pe as useBlocker, h as useFallbackStrings, fe as useLoaderData, C as useLocation, he as useNavigate, le as useParams, z as usePrefetch, u as useReconnect, R as useSearchParams, j as useServerRequest, x as useSetSearchParams, Ze as useTheme, S as withHash, w as withQuery };
@@ -1,5 +1,5 @@
1
1
  import { t as e } from "./frameworkBoot-C_d7E8Fj.js";
2
- import { o as t, r as n, t as r } from "./routerState-ga64vk2B.js";
2
+ import { o as t, r as n, t as r } from "./routerState-DpUqogK8.js";
3
3
  import { StrictMode as i, createElement as a } from "react";
4
4
  import { createRoot as o, hydrateRoot as s } from "react-dom/client";
5
5
  import { jsx as c } from "react/jsx-runtime";
package/dist/mount.js CHANGED
@@ -1,2 +1,2 @@
1
- import { t as e } from "./mount-D3k8Ozdm.js";
1
+ import { t as e } from "./mount-Gsn3ouYx.js";
2
2
  export { e as mount };
@@ -65,6 +65,7 @@ var e = /* @__PURE__ */ RegExp("\\u2028", "g"), t = /* @__PURE__ */ RegExp("\\u2
65
65
  ...e.pageLoaderRan ? { page: !0 } : {},
66
66
  ...t.length > 0 ? { segments: t } : {}
67
67
  },
68
+ ...e.pageClientOnly ? { pageClientOnly: !0 } : {},
68
69
  ...n || r ? { deferred: {
69
70
  ...n ? { page: e.deferredPageIds } : {},
70
71
  ...r ? { segments: e.deferredSegmentIds } : {}
@@ -85,6 +86,7 @@ var e = /* @__PURE__ */ RegExp("\\u2028", "g"), t = /* @__PURE__ */ RegExp("\\u2
85
86
  page: D(r.page, r.deferred?.page),
86
87
  pageRan: r.ran?.page === !0,
87
88
  segments: a.size > 0 ? a : T,
89
+ pageClientOnly: r.pageClientOnly === !0,
88
90
  pathname: t
89
91
  };
90
92
  }, D = (e, t) => {
@@ -1,6 +1,6 @@
1
1
  import { t as e } from "./globalContext-d4A-ugDg.js";
2
2
  import { c as t, n, t as r } from "./defaultFallbacks-CGs2z5qv.js";
3
- import { f as i, i as a } from "./routerState-ga64vk2B.js";
3
+ import { f as i, i as a } from "./routerState-DpUqogK8.js";
4
4
  import { Component as o, useCallback as s, useContext as c, useEffect as l, useMemo as u, useRef as d, useState as f } from "react";
5
5
  import { jsx as p, jsxs as ee } from "react/jsx-runtime";
6
6
  //#region src/routeEventBus.ts
@@ -286,7 +286,7 @@ var m = {
286
286
  }
287
287
  return this.props.children;
288
288
  }
289
- }, ve = ({ routes: e, notFounds: i = [], notFound: o = n, errorFallback: c = r }) => {
289
+ }, B = "data-voltro-page-slot", ve = () => /* @__PURE__ */ p("div", { [B]: "" }), ye = ({ routes: e, notFounds: i = [], notFound: o = n, errorFallback: c = r }) => {
290
290
  let [m, h] = f(() => window.location.pathname), [g, _] = f(() => window.location.pathname), [v, y] = f(() => window.location.search + window.location.hash), b = d(new le()), x = d(/* @__PURE__ */ new Map()), [C, w] = f(null), T = s((e, t) => (x.current.set(e, t), () => {
291
291
  x.current.delete(e);
292
292
  }), []), E = d(/* @__PURE__ */ new Map()), D = d(null), j = u(() => ie(e.map(re)), [e]);
@@ -351,7 +351,7 @@ var m = {
351
351
  };
352
352
  return document.addEventListener("click", e), () => document.removeEventListener("click", e);
353
353
  }, [P]);
354
- let I = u(() => O(j, m), [j, m]), R = d(/* @__PURE__ */ new Map()), [ve, ye] = f(0);
354
+ let I = u(() => O(j, m), [j, m]), R = d(/* @__PURE__ */ new Map()), [B, ye] = f(0);
355
355
  l(() => {
356
356
  let e = I?.route;
357
357
  if (!e?.load || R.current.has(e.pattern)) return;
@@ -362,27 +362,27 @@ var m = {
362
362
  t = !0;
363
363
  };
364
364
  }, [I?.route.load ? I.route.pattern : null]);
365
- let B = u(() => {
365
+ let V = u(() => {
366
366
  if (!I) return null;
367
367
  let e = I.route;
368
368
  if (!e.load) return e;
369
369
  let t = R.current.get(e.pattern);
370
370
  return t ? {
371
371
  ...e,
372
- ...Ee(t),
372
+ ...De(t),
373
373
  load: void 0
374
374
  } : null;
375
- }, [I, ve]), V = u(() => O(j, g), [j, g]), H = u(() => {
376
- if (!V) return null;
377
- let e = V.route;
375
+ }, [I, B]), H = u(() => O(j, g), [j, g]), U = u(() => {
376
+ if (!H) return null;
377
+ let e = H.route;
378
378
  if (!e.load) return e;
379
379
  let t = R.current.get(e.pattern);
380
380
  return t ? {
381
381
  ...e,
382
- ...Ee(t),
382
+ ...De(t),
383
383
  load: void 0
384
384
  } : null;
385
- }, [V, ve]), Se = s((e) => {
385
+ }, [H, B]), W = s((e) => {
386
386
  let t = new URL(e, window.location.origin), n = O(j, t.pathname);
387
387
  if (!n) return;
388
388
  let r = (e, r) => {
@@ -395,33 +395,37 @@ var m = {
395
395
  n.route.loader && r(k(n.route.pattern, n.params), n.route.loader), n.route.chain?.forEach((e, t) => {
396
396
  e.loader && r(ae(n.route.pattern, t, n.params), e.loader);
397
397
  });
398
- }, [j]), [U, W] = f({ status: "idle" }), G = d(void 0);
399
- G.current === void 0 && (G.current = a());
400
- let Ce = d(!1), [K, we] = f(() => ({
398
+ }, [j]), [G, K] = f({ status: "idle" }), q = d(void 0);
399
+ q.current === void 0 && (q.current = a());
400
+ let Se = d(!1), [Ce, we] = f(() => q.current?.pageClientOnly === !0 && q.current.pathname === m);
401
+ l(() => {
402
+ we(!1);
403
+ }, []);
404
+ let [J, Te] = f(() => ({
401
405
  pathname: m,
402
- data: me(G.current ?? null, m),
406
+ data: me(q.current ?? null, m),
403
407
  chain: I?.route.chain
404
408
  }));
405
409
  l(() => {
406
410
  let e = (e) => {
407
- _(m), we((t) => t.pathname === m && he(t.data, e) ? t : {
411
+ _(m), Te((t) => t.pathname === m && he(t.data, e) ? t : {
408
412
  pathname: m,
409
413
  data: e,
410
414
  chain: I?.route.chain
411
415
  });
412
416
  };
413
417
  if (!I) {
414
- W({ status: "idle" }), e(void 0);
418
+ K({ status: "idle" }), e(void 0);
415
419
  return;
416
420
  }
417
- if (!B) {
418
- W({
421
+ if (!V) {
422
+ K({
419
423
  status: "pending",
420
424
  key: k(I.route.pattern, I.params)
421
425
  });
422
426
  return;
423
427
  }
424
- let t = B, { params: n } = I, r = t.chain, i = (t.Pending ?? A(r, "Pending", r ? r.length - 1 : -1)) !== void 0, a = [];
428
+ let t = V, { params: n } = I, r = t.chain, i = (t.Pending ?? A(r, "Pending", r ? r.length - 1 : -1)) !== void 0, a = [];
425
429
  if (t.loader) {
426
430
  let e = t.loader;
427
431
  a.push({
@@ -448,12 +452,12 @@ var m = {
448
452
  });
449
453
  }
450
454
  }), a.length === 0) {
451
- W({ status: "idle" }), e(void 0);
455
+ K({ status: "idle" }), e(void 0);
452
456
  return;
453
457
  }
454
- let o = k(t.pattern, n), s = G.current;
455
- if (s && !Ce.current && m === s.pathname) {
456
- Ce.current = !0;
458
+ let o = k(t.pattern, n), s = q.current;
459
+ if (s && !Se.current && m === s.pathname) {
460
+ Se.current = !0;
457
461
  for (let e of a) e.target === "page" ? s.pageRan && b.current.seed(e.cacheKey, s.page) : s.segments.has(e.target) && b.current.seed(e.cacheKey, s.segments.get(e.target));
458
462
  }
459
463
  let c = a.map((e) => ({
@@ -479,18 +483,18 @@ var m = {
479
483
  };
480
484
  }, u = l();
481
485
  if (u) {
482
- u.ok ? (W({
486
+ u.ok ? (K({
483
487
  status: "success",
484
488
  key: o,
485
489
  data: u.data
486
- }), e(u.data)) : (W({
490
+ }), e(u.data)) : (K({
487
491
  status: "error",
488
492
  key: o,
489
493
  error: u.error
490
494
  }), S(u.error) || e(void 0));
491
495
  return;
492
496
  }
493
- W({
497
+ K({
494
498
  status: "pending",
495
499
  key: o
496
500
  }), i && _(m);
@@ -498,11 +502,11 @@ var m = {
498
502
  return Promise.all(f).then(() => {
499
503
  if (d) return;
500
504
  let t = l();
501
- t && (t.ok ? (W({
505
+ t && (t.ok ? (K({
502
506
  status: "success",
503
507
  key: o,
504
508
  data: t.data
505
- }), e(t.data)) : (W({
509
+ }), e(t.data)) : (K({
506
510
  status: "error",
507
511
  key: o,
508
512
  error: t.error
@@ -512,48 +516,48 @@ var m = {
512
516
  };
513
517
  }, [
514
518
  I,
515
- B,
519
+ V,
516
520
  m
517
521
  ]);
518
- let q = K.data?.page, J = g === m, Y = K.pathname === g, Te = Y ? K.data?.page : void 0, De = ge(), X = u(() => {
519
- if (!V) return null;
520
- let e = V.params.locale ?? De;
521
- return se(H?.meta, V.params, q, e);
522
+ let Y = J.data?.page, X = g === m, Ee = J.pathname === g, Oe = Ee ? J.data?.page : void 0, ke = ge(), Z = u(() => {
523
+ if (!H) return null;
524
+ let e = H.params.locale ?? ke;
525
+ return se(U?.meta, H.params, Y, e);
522
526
  }, [
523
- V,
524
527
  H,
525
- q,
526
- De
528
+ U,
529
+ Y,
530
+ ke
527
531
  ]);
528
- l(() => _e(X), [X]);
529
- let [Oe, Z] = f(""), Q = d(!1), [ke, Ae] = f(!1);
532
+ l(() => _e(Z), [Z]);
533
+ let [Q, Ae] = f(""), je = d(!1), [Me, Ne] = f(!1);
530
534
  l(() => {
531
- if (!Q.current) {
532
- Q.current = !0, Ae(!0);
535
+ if (!je.current) {
536
+ je.current = !0, Ne(!0);
533
537
  return;
534
538
  }
535
- let e = X?.title ?? (typeof document < "u" ? document.title : "");
536
- Z(e && e.length > 0 ? e : g);
537
- }, [g, X]), l(() => {
539
+ let e = Z?.title ?? (typeof document < "u" ? document.title : "");
540
+ Ae(e && e.length > 0 ? e : g);
541
+ }, [g, Z]), l(() => {
538
542
  let e = D.current;
539
543
  e && (D.current = null, de(() => window.scrollTo(e.x, e.y)));
540
544
  }, [g]);
541
- let je = u(() => ({
545
+ let Pe = u(() => ({
542
546
  pathname: g,
543
547
  search: v,
544
548
  navigate: P,
545
- params: V?.params ?? {},
546
- prefetch: Se,
547
- loaderData: q,
549
+ params: H?.params ?? {},
550
+ prefetch: W,
551
+ loaderData: Y,
548
552
  registerBlocker: T,
549
553
  blocked: C
550
554
  }), [
551
555
  g,
552
556
  v,
553
557
  P,
554
- V,
555
- Se,
556
- q,
558
+ H,
559
+ W,
560
+ Y,
557
561
  T,
558
562
  C
559
563
  ]);
@@ -571,82 +575,83 @@ var m = {
571
575
  }, []), l(() => {
572
576
  te({
573
577
  pathname: g,
574
- params: V?.params ?? {},
575
- loaderData: q
578
+ params: H?.params ?? {},
579
+ loaderData: Y
576
580
  });
577
581
  }, [
578
582
  g,
579
- V,
580
- q
583
+ H,
584
+ Y
581
585
  ]), l(() => {
582
- if (U.status === "pending") return t({
586
+ if (G.status === "pending") return t({
583
587
  id: "voltro:router:loader",
584
588
  kind: "loading",
585
589
  label: "Loading…"
586
590
  });
587
- }, [U.status]), l(() => {
588
- if (U.status === "error") return t({
591
+ }, [G.status]), l(() => {
592
+ if (G.status === "error") return t({
589
593
  id: "voltro:router:loader-error",
590
594
  kind: "error",
591
595
  label: "Loader failed"
592
596
  });
593
- }, [U.status]), l(() => {
594
- U.status === "error" && S(U.error) && P(U.error.location, { replace: !0 });
595
- }, [U, P]);
596
- let Me = () => {
597
+ }, [G.status]), l(() => {
598
+ G.status === "error" && S(G.error) && P(G.error.location, { replace: !0 });
599
+ }, [G, P]);
600
+ let Fe = () => {
597
601
  let e = ce(i, g);
598
602
  if (e) {
599
603
  let { Component: t, chain: n } = e;
600
604
  return xe(n, /* @__PURE__ */ p(t, {}), g, c);
601
605
  }
602
606
  return /* @__PURE__ */ p(o, {});
603
- }, Ne = J && V && U.status === "error" ? U.error : void 0, $;
604
- if (!V) $ = Me();
605
- else if (ne(Ne)) $ = Me();
607
+ }, Ie = X && H && G.status === "error" ? G.error : void 0, $;
608
+ if (!H) $ = Fe();
609
+ else if (ne(Ie)) $ = Fe();
606
610
  else {
607
- let e = V.route.chain, t = e ? e.length - 1 : -1, n = H?.ErrorBoundary ?? A(e, "Error", t) ?? c, r;
608
- if (J && U.status === "error" && !S(U.error)) r = /* @__PURE__ */ p(n, {
609
- error: U.error,
611
+ let e = H.route.chain, t = e ? e.length - 1 : -1, n = U?.ErrorBoundary ?? A(e, "Error", t) ?? c, r;
612
+ if (Ce) r = /* @__PURE__ */ p(ve, {});
613
+ else if (X && G.status === "error" && !S(G.error)) r = /* @__PURE__ */ p(n, {
614
+ error: G.error,
610
615
  reset: () => {
611
- b.current.invalidate(V.route.pattern), h((e) => e), W({ status: "idle" });
616
+ b.current.invalidate(H.route.pattern), h((e) => e), K({ status: "idle" });
612
617
  }
613
618
  });
614
- else if (J && U.status === "pending") {
615
- let i = H?.Pending ?? A(e, "Pending", t);
619
+ else if (X && G.status === "pending") {
620
+ let i = U?.Pending ?? A(e, "Pending", t);
616
621
  if (i) r = /* @__PURE__ */ p(i, {});
617
- else if (H?.Component) {
618
- let e = H.Component;
622
+ else if (U?.Component) {
623
+ let e = U.Component;
619
624
  r = /* @__PURE__ */ p(z, {
620
625
  resetKey: g,
621
626
  Fallback: n,
622
627
  children: /* @__PURE__ */ p(L.Provider, {
623
- value: Te,
628
+ value: Oe,
624
629
  children: /* @__PURE__ */ p(e, {})
625
630
  })
626
631
  });
627
632
  } else r = null;
628
633
  } else {
629
- let e = H?.Component;
634
+ let e = U?.Component;
630
635
  r = e ? /* @__PURE__ */ p(z, {
631
636
  resetKey: g,
632
637
  Fallback: n,
633
638
  children: /* @__PURE__ */ p(L.Provider, {
634
- value: Te,
639
+ value: Oe,
635
640
  children: /* @__PURE__ */ p(e, {})
636
641
  })
637
642
  }) : null;
638
643
  }
639
644
  $ = xe(e, r, g, c, (t) => {
640
- if (Y) return K.data?.segments[t];
645
+ if (Ee) return J.data?.segments[t];
641
646
  let n = e?.[t]?.Layout;
642
- return n !== void 0 && n === K.chain?.[t]?.Layout ? K.data?.segments[t] : void 0;
647
+ return n !== void 0 && n === J.chain?.[t]?.Layout ? J.data?.segments[t] : void 0;
643
648
  });
644
649
  }
645
650
  return /* @__PURE__ */ ee(F.Provider, {
646
- value: je,
647
- children: [$, ke ? /* @__PURE__ */ p(be, { message: Oe }) : null]
651
+ value: Pe,
652
+ children: [$, Me ? /* @__PURE__ */ p(be, { message: Q }) : null]
648
653
  });
649
- }, ye = {
654
+ }, V = {
650
655
  position: "absolute",
651
656
  width: 1,
652
657
  height: 1,
@@ -661,7 +666,7 @@ var m = {
661
666
  role: "status",
662
667
  "aria-live": "assertive",
663
668
  "aria-atomic": "true",
664
- style: ye,
669
+ style: V,
665
670
  children: e
666
671
  }), xe = (e, t, n, r, i) => {
667
672
  if (!e || e.length === 0) return t;
@@ -685,7 +690,7 @@ var m = {
685
690
  }
686
691
  }
687
692
  return a;
688
- }, B = () => I().pathname, V = () => I().navigate, H = () => I().params, Se = () => c(L), U = () => I().prefetch, W = (e) => {
693
+ }, H = () => I().pathname, U = () => I().navigate, W = () => I().params, G = () => c(L), K = () => I().prefetch, q = (e) => {
689
694
  let { registerBlocker: t, blocked: n } = I(), r = d(Symbol("voltro:blocker")), i = d(e);
690
695
  return i.current = e, l(() => {
691
696
  let e = r.current;
@@ -699,22 +704,22 @@ var m = {
699
704
  retry: n.retry,
700
705
  reset: n.reset
701
706
  } : { blocked: !1 };
702
- }, G = (e) => e instanceof URLSearchParams ? e : new URLSearchParams(e), Ce = () => {
707
+ }, Se = (e) => e instanceof URLSearchParams ? e : new URLSearchParams(e), Ce = () => {
703
708
  let { navigate: e } = I();
704
709
  return s((t, n) => {
705
- let r = new URLSearchParams(typeof window < "u" ? window.location.search : ""), i = G(typeof t == "function" ? t(r) : t).toString(), a = typeof window < "u" ? window.location.pathname : "/", o = i ? `${a}?${i}` : a;
710
+ let r = new URLSearchParams(typeof window < "u" ? window.location.search : ""), i = Se(typeof t == "function" ? t(r) : t).toString(), a = typeof window < "u" ? window.location.pathname : "/", o = i ? `${a}?${i}` : a;
706
711
  e(o, {
707
712
  replace: !n?.push,
708
713
  ...n?.scroll === void 0 ? {} : { scroll: n.scroll }
709
714
  });
710
715
  }, [e]);
711
- }, K = (e) => e, we = (e, t) => {
716
+ }, we = (e) => e, J = (e, t) => {
712
717
  let n = new URLSearchParams();
713
718
  for (let [e, r] of Object.entries(t)) r !== void 0 && n.append(e, String(r));
714
719
  let r = n.toString();
715
720
  return r ? `${e}?${r}` : e;
716
- }, q = (e, t) => `${e}#${t.replace(/^#/, "")}`, J = (e) => /^([a-z][a-z0-9+.-]*:|\/\/|#)/i.test(e), Y = ({ to: e, children: t, onClick: n, prefetch: r = !1, replace: i = !1, onMouseEnter: a, onFocus: o, ...s }) => {
717
- let c = V(), l = U(), u = J(e), d = (t) => {
721
+ }, Te = (e, t) => `${e}#${t.replace(/^#/, "")}`, Y = (e) => /^([a-z][a-z0-9+.-]*:|\/\/|#)/i.test(e), X = ({ to: e, children: t, onClick: n, prefetch: r = !1, replace: i = !1, onMouseEnter: a, onFocus: o, ...s }) => {
722
+ let c = U(), l = K(), u = Y(e), d = (t) => {
718
723
  u || t.metaKey || t.ctrlKey || t.shiftKey || t.altKey || t.button !== 0 || (t.preventDefault(), n?.(t), c(e, i ? { replace: i } : void 0));
719
724
  }, f = (t) => {
720
725
  r && !u && l(e), a?.(t);
@@ -729,10 +734,10 @@ var m = {
729
734
  onFocus: ee,
730
735
  children: t
731
736
  });
732
- }, Te = (e) => Y({
737
+ }, Ee = (e) => X({
733
738
  ...e,
734
739
  to: e.to
735
- }), Ee = (e) => ({
740
+ }), De = (e) => ({
736
741
  Component: e.default,
737
742
  meta: e.meta,
738
743
  loader: e.loader,
@@ -740,30 +745,30 @@ var m = {
740
745
  Pending: e.Pending,
741
746
  renderMode: e.renderMode,
742
747
  interactive: e.interactive
743
- }), De = (e, t, n = {}) => ({
748
+ }), Oe = (e, t, n = {}) => ({
744
749
  pattern: e,
745
- ...Ee(t),
750
+ ...De(t),
746
751
  chain: n.chain
747
- }), X = (e, t, n = {}) => ({
752
+ }), ke = (e, t, n = {}) => ({
748
753
  pattern: e,
749
754
  load: t,
750
755
  chain: n.chain
751
- }), Oe = async (e, t) => {
756
+ }), Z = async (e, t) => {
752
757
  let n = O(e.map(re), t)?.route.load;
753
758
  if (n) try {
754
759
  await n();
755
760
  } catch {}
756
- }, Z = e("serverRequest", null), Q = () => c(Z), ke = () => {
757
- let e = Q();
761
+ }, Q = e("serverRequest", null), Ae = () => c(Q), je = () => {
762
+ let e = Ae();
758
763
  if (e !== null) {
759
764
  let t = e.url.indexOf("?");
760
765
  return new URLSearchParams(t >= 0 ? e.url.slice(t + 1) : "");
761
766
  }
762
767
  return typeof window < "u" ? new URLSearchParams(window.location.search) : new URLSearchParams();
763
- }, Ae = ({ value: e, children: t }) => /* @__PURE__ */ p(Z.Provider, {
768
+ }, Me = ({ value: e, children: t }) => /* @__PURE__ */ p(Q.Provider, {
764
769
  value: e,
765
770
  children: t
766
- }), je = (e) => {
771
+ }), Ne = (e) => {
767
772
  let t = {};
768
773
  if (!e) return t;
769
774
  for (let n of e.split(";")) {
@@ -781,4 +786,4 @@ var m = {
781
786
  return t;
782
787
  };
783
788
  //#endregion
784
- export { Ce as A, te as B, ie as C, V as D, B as E, ne as F, S as I, b as L, we as M, v as N, H as O, y as P, x as R, ae as S, Se as T, _ as V, O as _, Q as a, ue as b, L as c, F as d, re as f, k as g, X as h, ke as i, q as j, U as k, Te as l, ce as m, Ae as n, Y as o, K as p, je as r, le as s, Z as t, ve as u, De as v, W as w, se as x, Oe as y, g as z };
789
+ export { W as A, x as B, se as C, G as D, q as E, v as F, te as H, y as I, ne as L, Ce as M, Te as N, H as O, J as P, S as R, ue as S, ie as T, _ as U, g as V, ke as _, Ae as a, Oe as b, L as c, Ee as d, ye as f, ce as g, we as h, je as i, K as j, U as k, B as l, re as m, Me as n, X as o, F as p, Ne as r, le as s, Q as t, ve as u, k as v, ae as w, Z as x, O as y, b as z };
package/dist/ssr.d.ts CHANGED
@@ -77,6 +77,13 @@ export declare interface InlinedRouterState {
77
77
  readonly page?: boolean;
78
78
  readonly segments?: ReadonlyArray<number>;
79
79
  };
80
+ /** True when the server rendered the layout chain but left the page leaf as
81
+ * an EMPTY slot — the `renderMode:'spa'`-under-an-SSR-layout case. The
82
+ * server ran only the layout loaders; the page is client-only. The client's
83
+ * FIRST render must reproduce the identical empty slot so the layout chain
84
+ * hydrates without a mismatch, then mount the real page after commit. Absent
85
+ * (falsy) for every full-page SSR/ISR/static document. */
86
+ readonly pageClientOnly?: boolean;
80
87
  /** Deferred (streamed) loader fields: field name -> client-registry id, for
81
88
  * the page loader and per chain index. The VALUES are not here — they
82
89
  * arrive later, published by the settle `<script>` each `<Await>` boundary
@@ -319,6 +326,14 @@ export declare interface RenderPageOptions {
319
326
  * catalog at meta-resolve time. Optional: when absent, page
320
327
  * `meta(ctx)` callbacks see `ctx.locale = 'en'` as a safe default. */
321
328
  readonly locale?: string;
329
+ /** Render the LAYOUT CHAIN ONLY, with an empty page slot at the leaf
330
+ * ({@link PageSlot}) instead of the page Component — the
331
+ * `renderMode:'spa'`-under-an-SSR-layout case. The caller runs only the
332
+ * layout loaders (not the page loader) and pairs this with a state script
333
+ * carrying `pageClientOnly: true`, so the client reproduces the empty slot
334
+ * on its first render and mounts the page after hydration. When set, the
335
+ * descriptor need not carry a `Component`. */
336
+ readonly pageSlot?: boolean;
322
337
  }
323
338
 
324
339
  export declare interface RenderPageResult {
@@ -415,6 +430,12 @@ export declare interface RouterStateInput {
415
430
  * `loaderData` (a loader may legitimately resolve to `undefined`/`null`,
416
431
  * and the SSR paths default the field to `null` when there is no loader). */
417
432
  readonly pageLoaderRan: boolean;
433
+ /** Set by the SSR-layout-shell path (a `renderMode:'spa'` page under an SSR
434
+ * layout chain): the server rendered the layouts + an empty page slot and
435
+ * ran only the layout loaders. Tells the client to reproduce the empty slot
436
+ * on its first render, then mount the page after hydration. Omit for every
437
+ * full-page render. */
438
+ readonly pageClientOnly?: boolean | undefined;
418
439
  /** Page-loader deferred fields: name -> registry id. Produced by
419
440
  * `prepareDeferredLoaderData` in the SSR pipeline; omit when nothing
420
441
  * deferred (which is every non-streaming emitter). */
package/dist/ssr.js CHANGED
@@ -1,21 +1,21 @@
1
- import { _ as e, a as t, g as n, m as r, n as i, p as a, s as o, t as s } from "./routerState-ga64vk2B.js";
2
- import { c, d as l, r as u, t as d, x as f } from "./serverContext-BW0GF8fv.js";
3
- import { createElement as p } from "react";
4
- import { renderToPipeableStream as m, renderToString as h } from "react-dom/server";
1
+ import { _ as e, a as t, g as n, m as r, n as i, p as a, s as o, t as s } from "./routerState-DpUqogK8.js";
2
+ import { C as c, c as l, p as u, r as d, t as f, u as p } from "./serverContext-COzippNt.js";
3
+ import { createElement as m } from "react";
4
+ import { renderToPipeableStream as h, renderToString as g } from "react-dom/server";
5
5
  //#region src/ssr.tsx
6
- var g = (e, t, n, r) => {
7
- let i = p(c.Provider, { value: n }, p(e));
6
+ var _ = (e, t, n, r) => {
7
+ let i = m(l.Provider, { value: n }, m(e));
8
8
  if (!t) return i;
9
9
  for (let e = t.length - 1; e >= 0; e--) {
10
10
  let n = t[e];
11
11
  if (n.Layout) {
12
12
  let t = n.Layout;
13
- i = p(c.Provider, { value: r(e) }, p(t, { children: i }));
13
+ i = m(l.Provider, { value: r(e) }, m(t, { children: i }));
14
14
  }
15
15
  }
16
16
  return i;
17
- }, _ = (e) => {
18
- let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: c } = e, u = f(t.meta, n, i, c), m = {
17
+ }, v = (e) => {
18
+ let { descriptor: t, params: n, pathname: r, loaderData: i, segmentLoaderData: a, requestContext: o, outerWrap: s, locale: l, pageSlot: d } = e, h = c(t.meta, n, i, l), g = {
19
19
  pathname: r,
20
20
  search: "",
21
21
  params: n,
@@ -26,23 +26,23 @@ var g = (e, t, n, r) => {
26
26
  prefetch: () => {},
27
27
  registerBlocker: () => () => {},
28
28
  blocked: null
29
- }, h = t.Component;
30
- if (!h) throw Error(`SSR received a descriptor with no Component for "${r}". Lazy page routes are a client-only optimization and must not be used on the server.`);
31
- let _ = p(l.Provider, { value: m }, g(h, t.chain, i, (e) => a?.[e]));
32
- return o && (_ = p(d.Provider, { value: o }, _)), s && (_ = s(_)), {
33
- tree: _,
34
- meta: u
29
+ }, v = d ? p : t.Component;
30
+ if (!v) throw Error(`SSR received a descriptor with no Component for "${r}". Lazy page routes are a client-only optimization and must not be used on the server.`);
31
+ let y = m(u.Provider, { value: g }, _(v, t.chain, d ? void 0 : i, (e) => a?.[e]));
32
+ return o && (y = m(f.Provider, { value: o }, y)), s && (y = s(y)), {
33
+ tree: y,
34
+ meta: h
35
35
  };
36
- }, v = (e) => {
37
- let { tree: t, meta: n } = _(e);
36
+ }, y = (e) => {
37
+ let { tree: t, meta: n } = v(e);
38
38
  return {
39
- html: h(t),
39
+ html: g(t),
40
40
  meta: n
41
41
  };
42
- }, y = (e) => {
43
- let { tree: t, meta: n } = _(e);
42
+ }, b = (e) => {
43
+ let { tree: t, meta: n } = v(e);
44
44
  return {
45
- stream: m(t, {
45
+ stream: h(t, {
46
46
  ...e.bootstrapModules ? { bootstrapModules: [...e.bootstrapModules] } : {},
47
47
  ...e.onShellReady ? { onShellReady: e.onShellReady } : {},
48
48
  ...e.onShellError ? { onShellError: e.onShellError } : {},
@@ -50,20 +50,20 @@ var g = (e, t, n, r) => {
50
50
  }),
51
51
  meta: n
52
52
  };
53
- }, b = (e) => {
53
+ }, x = (e) => {
54
54
  if (!e) return "";
55
55
  let t = [];
56
- if (typeof e.title == "string" && t.push(`<title>${x(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${S(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${S(e.canonical)}" />`), e.tags) for (let n of e.tags) {
57
- let e = n.name ? `name="${S(n.name)}"` : n.property ? `property="${S(n.property)}"` : "";
58
- e && t.push(`<meta ${e} content="${S(n.content)}" />`);
56
+ if (typeof e.title == "string" && t.push(`<title>${S(e.title)}</title>`), e.description && t.push(`<meta name="description" content="${C(e.description)}" />`), e.canonical && t.push(`<link rel="canonical" href="${C(e.canonical)}" />`), e.tags) for (let n of e.tags) {
57
+ let e = n.name ? `name="${C(n.name)}"` : n.property ? `property="${C(n.property)}"` : "";
58
+ e && t.push(`<meta ${e} content="${C(n.content)}" />`);
59
59
  }
60
60
  if (e.links) for (let n of e.links) {
61
61
  let e = [
62
- `rel="${S(n.rel)}"`,
63
- `href="${S(n.href)}"`,
64
- n.hreflang ? `hreflang="${S(n.hreflang)}"` : "",
65
- n.type ? `type="${S(n.type)}"` : "",
66
- n.title ? `title="${S(n.title)}"` : ""
62
+ `rel="${C(n.rel)}"`,
63
+ `href="${C(n.href)}"`,
64
+ n.hreflang ? `hreflang="${C(n.hreflang)}"` : "",
65
+ n.type ? `type="${C(n.type)}"` : "",
66
+ n.title ? `title="${C(n.title)}"` : ""
67
67
  ].filter(Boolean).join(" ");
68
68
  t.push(`<link ${e} />`);
69
69
  }
@@ -72,6 +72,6 @@ var g = (e, t, n, r) => {
72
72
  t.push(`<script type="application/ld+json" data-voltro-page-jsonld>${e}<\/script>`);
73
73
  }
74
74
  return t.join("\n ");
75
- }, x = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), S = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
75
+ }, S = (e) => e.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"), C = (e) => e.replace(/&/g, "&amp;").replace(/"/g, "&quot;").replace(/</g, "&lt;");
76
76
  //#endregion
77
- export { s as ROUTER_STATE_SCRIPT_ID, o as assertDeferralSupported, i as encodeRouterState, a as isDeferredLoaderResult, u as parseCookieHeader, r as prepareDeferredLoaderData, n as renderDeferredRegistryScript, b as renderMetaToHtml, v as renderPageToHtml, y as renderPageToStream, t as renderRouterStateScript, e as serialiseStateForInlining };
77
+ export { s as ROUTER_STATE_SCRIPT_ID, o as assertDeferralSupported, i as encodeRouterState, a as isDeferredLoaderResult, d as parseCookieHeader, r as prepareDeferredLoaderData, n as renderDeferredRegistryScript, x as renderMetaToHtml, y as renderPageToHtml, b as renderPageToStream, t as renderRouterStateScript, e as serialiseStateForInlining };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voltro/web",
3
- "version": "0.8.0",
3
+ "version": "0.9.0",
4
4
  "description": "The Voltro web framework — file-based routing, render modes (SSR / SSG / islands), the page-export contract, data hooks, and the browser mount.",
5
5
  "keywords": [
6
6
  "voltro",
@@ -52,8 +52,8 @@
52
52
  "node": ">=24.0.0"
53
53
  },
54
54
  "dependencies": {
55
- "@voltro/client": "0.8.0",
56
- "@voltro/ui": "0.8.0"
55
+ "@voltro/client": "0.9.0",
56
+ "@voltro/ui": "0.9.0"
57
57
  },
58
58
  "peerDependencies": {
59
59
  "@effect/platform": "^0.96.2",