@symbo.ls/brender 3.14.666 → 3.14.667

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
@@ -1,5 +1,18 @@
1
1
  # @symbo.ls/brender
2
2
 
3
+ ## 3.14.667
4
+
5
+ ### Patch Changes
6
+
7
+ - Server renders run one at a time per process, and a page at /<lang>/… starts in that language.
8
+
9
+ - brender: `render()` (and `renderRoute` / `renderPage`) run one at a time per process; a per-render `timeout` (default 30 s, `0`/`false` turns it off) rejects with `BRENDER_RENDER_TIMEOUT`, puts the render's globals back and lets the next render start.
10
+ - brender + mermaid: before the app starts, the render stores the address's language (the first path segment, when it is one of the project's languages) under polyglot's key, so the fetch seed is recorded in the language the browser asks in; the served page's start-up script stores the page's language. One helper in utils (`polyglotAddressLang`, `polyglotStartupScript`).
11
+ - cli: the publish pre-render passes its route limit to brender's `timeout`; `symbols.json` `brender.timeout` sets each page's render limit for `smbls build` and `smbls brender`.
12
+
13
+ - Updated dependencies
14
+ - @symbo.ls/utils@3.14.788
15
+
3
16
  ## 3.14.666
4
17
 
5
18
  ### Patch Changes
package/README.md CHANGED
@@ -119,6 +119,10 @@ const result = await render(data, { route: '/about' })
119
119
  // result.element -> root DOMQL element
120
120
  ```
121
121
 
122
+ **One render at a time.** A render puts its environment on the process's bare globals (`document`, `location`, `localStorage`, `sessionStorage`, `window`) because the framework reads them bare, so `render()` — and `renderRoute()` / `renderPage()`, which call it — runs one render at a time per process: a call made while another render runs waits until that one has ended (returned, thrown or timed out), then starts. Calls made one after another (`smbls build`, `smbls brender`) do not wait. N renders started together take as long as N renders one after another.
123
+
124
+ **`timeout`** (default `30000` ms; `0` or `false`: none — then a render that never ends holds every later one) is how long a render may run once its turn starts — the wait for earlier renders does not count, and neither does building the process's framework bundle on its first render. A render past it is abandoned: the call rejects with an error whose `code` is `BRENDER_RENDER_TIMEOUT` and whose message, `route` and `pathname` name the page; the bare globals it put in place go back; the next render starts. When the wait it hung on ends later, its own remaining steps stop: it never puts its globals back over a later render's, and never makes an environment of its own after its time. What it cannot stop: framework or page code of the abandoned render that resumes with that wait reads and writes the bare globals of whatever render runs then, and process-wide framework state (the active root state behind `getRootState()` without `this`). Until it has unwound, a render that runs meanwhile carries no fetch seed and `diagnostics: null`: its recording could hold the abandoned render's requests.
125
+
122
126
  ### `renderPage(data, route, options?)`
123
127
 
124
128
  Renders a complete, ready-to-serve HTML page. Combines `render()` output with metadata, CSS (atomic + global), fonts, and optional ISR client bundle.
@@ -148,6 +152,7 @@ Options:
148
152
  | `eagerImages` | `2` | Images past the first `eagerImages` in the markup get `loading="lazy"`, unless they name their own `loading`. `false` turns it off. |
149
153
  | `lang` | the page's | `<html lang>`. With `shell` and a project with languages, the page's language (the root state's `lang`). |
150
154
  | `prefetch` | `true` | Fetch table/RPC labels before the render and wait for the page's requests (up to 2 s). `false` fetches no labels first and waits only a 50 ms quiet period. Either way the page's data comes from its `fetch` declarations during the render. |
155
+ | `timeout` | `30000` | Milliseconds the render may run once its turn starts; past it the render is abandoned and the call rejects (`BRENDER_RENDER_TIMEOUT`). `0` or `false`: no limit. See `render()`. |
151
156
  | `depth` | `0` | Directory depth of the output file: rebases the shell's relative script/link URLs |
152
157
  | `adoptDom` | `false` | Ship the element-path registry, so the client adopts the markup instead of replacing it (`smbls build` passes it unless `brender: { hydrate: false }`) |
153
158
  | `isr` | — | `{ clientScript }`: a standalone document that boots this client bundle |
@@ -157,7 +162,9 @@ Options:
157
162
 
158
163
  **Data and labels.** A page's declarative `fetch` runs during the render, through the fetch plugin, with the real element: its `transform(data, el, state)`, and `children` re-run on the answer. The render waits for the requests it starts. Labels: a table/RPC `polyglot.fetch` (`{ rpc }`) is fetched before the render, for every language; a FUNCTION `polyglot.fetch` is the project's own loader and runs inside the render, called by the polyglot plugin with the root element. Labels resolve in the page's language — the root state's `lang` (a router guard that reads `/en/…` sets it), else `polyglot.defaultLang`.
159
164
 
160
- **Diagnostics.** `result.diagnostics` names what a page was made without: `failedRequests` (`{ url, reason }`, origin and path only — a query can carry a key) and `rawLabelKeys` (label keys the page shows raw). `null` when another render overlapped this one in the same process. `smbls build` prints them; `smbls build --strict` fails on them.
165
+ **The address's language.** A page at an address whose first segment is one of the project's languages (`/en/about` with `polyglot.languages: ['ka', 'en']`) starts in that language: before the app starts, the render stores it in its own environment's storage under polyglot's key (`storageLangKey`, default `smbls_lang`) — where a site's pre-boot script puts it in the browser — so polyglot starts in it and the shell's first requests (a header's menu, created before any router guard runs) are made, and recorded in the fetch seed, in the page's language. The seed is keyed by address and language: the browser adopts those answers only if it starts in the same language, so a site with language-prefixed addresses stores the address's language before its app starts (a pre-boot script that runs before the app's own). An address with no language segment starts as before: the stored choice, else the root state's `lang`, else `polyglot.defaultLang`. A standalone page with an ISR client (`isr`) starts its client itself: for a visitor with no language stored yet, its start-up script stores the page's language (it stored `polyglot.defaultLang`), so that client starts where the server started.
166
+
167
+ **Diagnostics.** `result.diagnostics` names what a page was made without: `failedRequests` (`{ url, reason }`, origin and path only — a query can carry a key) and `rawLabelKeys` (label keys the page shows raw). `null` when another render overlapped this one in the same process (since renders run one at a time, only a render abandoned at its timeout and still unwinding can). `smbls build` prints them; `smbls build --strict` fails on them.
161
168
 
162
169
  **Every render is the same render.** Each render processes the design system on a config of its own, the way the page's single boot does (`context.isolateDesignSystem`), with its own `localStorage`/`sessionStorage`, so the tenth page of a build has the theme variables, language and labels of the first. Each render also has a `@symbo.ls/fetch` query cache of its own (`context.isolateFetchCache`): a page makes every request its markup depends on itself, so its fetch seed carries every answer it shows, even when an earlier page of the build read the same key; and renders in one process — one after another or at once, of one project or of several — never share rows, requests in flight or invalidations. Its queries bind no window focus, reconnect or `refetchInterval` refetch: once its markup is taken, a render's queries leave no listener, no timer and no request behind. A process builds the framework bundle once, also when its first renders start together.
163
170
 
@@ -281,7 +288,7 @@ Output directory defaults to `dist-brender` (or `brenderDistDir` from `symbols.j
281
288
 
282
289
  Param routes (e.g. `/blog/:id`) are skipped unless `brender.paths` lists addresses for them.
283
290
 
284
- `smbls brender` and `smbls build` share one route loop: the same `brender` settings from `symbols.json` (`context`, `paths`, `exclude`, `strict`, `eagerImages`), `file:` URLs resolved into the output (a `file:` URL that names no project file fails its route), and the same diagnostics. `smbls brender` writes standalone pages in a directory layout (`/about` → `about/index.html`), each with the CSS of every page it rendered.
291
+ `smbls brender` and `smbls build` share one route loop: the same `brender` settings from `symbols.json` (`context`, `paths`, `exclude`, `strict`, `eagerImages`, `timeout`), `file:` URLs resolved into the output (a `file:` URL that names no project file fails its route), and the same diagnostics. `smbls brender` writes standalone pages in a directory layout (`/about` → `about/index.html`), each with the CSS of every page it rendered.
285
292
 
286
293
  ### `smbls build`
287
294
 
@@ -312,7 +319,8 @@ With brender on (the default), `smbls build` pre-renders every static route INTO
312
319
  "exclude": ["/admin*"],
313
320
  "paths": { "/:lang": ["/en"], "/:lang/about": ["/en/about"] },
314
321
  "strict": true,
315
- "eagerImages": 2
322
+ "eagerImages": 2,
323
+ "timeout": 30000
316
324
  }
317
325
  }
318
326
  ```
@@ -325,6 +333,7 @@ With brender on (the default), `smbls build` pre-renders every static route INTO
325
333
  | `strict` | As `--strict`. |
326
334
  | `eagerImages` | As renderPage's. |
327
335
  | `hydrate` | `true` (default): every page ships its element-path registry and the client adopts it; `false`: replace pages (no registry; the client renders fresh). |
336
+ | `timeout` | Each page's render time limit in ms, as render()'s `timeout` (default `30000`; `0`: none). A page past it fails with `BRENDER_RENDER_TIMEOUT` and the next page renders. `smbls build` and `smbls brender` pass it to every render. |
328
337
 
329
338
  ## Examples
330
339
 
@@ -429,7 +438,7 @@ The client finds an element's node through the registry the page carries (elemen
429
438
  - `renderElement()` uses `@domql/element` create directly — lightweight, no smbls bootstrap. Good for individual components
430
439
  - `render()` uses the full `smbls/src/createDomql.js` pipeline — handles routing, designSystem initialization, uikit defaults, the works. Needed for complete apps
431
440
  - The smbls source is bundled with esbuild (cached after first call) because the monorepo uses extensionless/directory imports that Node.js ESM can't resolve natively. The esbuild plugin patches SSR-incompatible code (window references, createRequire, circular imports) and stubs out browser-only packages
432
- - `render()` sets `globalThis.document` and `globalThis.location` before each render to match the linkedom virtual env, then restores them after. This allows the bundled smbls code (which reads `window = globalThis`) to work in SSR
441
+ - `render()` sets `globalThis.document`, `globalThis.location`, `globalThis.localStorage` and `globalThis.sessionStorage` before each render to match the linkedom virtual env, then restores them after (or at its timeout). This allows the bundled smbls code (which reads `window = globalThis`) to work in SSR. Renders run one at a time per process, so no two hold these globals at once
433
442
  - `hydrate.js` is browser-only code (no linkedom dependency) — it's exported separately via `@symbo.ls/brender/hydrate`
434
443
  - `createEnv()` sets `globalThis.window/document/Node/HTMLElement` because `@domql/utils` `isDOMNode` uses `instanceof` checks against global constructors
435
444
  - CSS is extracted from the atomic CSS engine's generated rules
@@ -1,21 +1,21 @@
1
- import{resolve as m,join as Ce,dirname as x}from"path";import{existsSync as b,unlinkSync as De,readFileSync as W,realpathSync as Ie}from"fs";import{resolvePackageEntry as Oe}from"./packageEntry.js";import{tmpdir as Me}from"os";import{randomBytes as Pe}from"crypto";import{createRequire as oe}from"module";import{pathToFileURL as Fe}from"url";import{createEnv as ue}from"./env.js";import{resetKeys as de,assignKeys as me,mapKeysToElements as ge}from"./keys.js";import{extractMetadata as se,generateHeadHtml as Be}from"./metadata.js";import"./hydrate.js";import{composeIntoShell as Ke}from"./shell.js";import{fetchSSRTranslations as Ne}from"./prefetch.js";import{createEarlySeedScript as Ge}from"@symbo.ls/fetch";import{headerEntries as He}from"@symbo.ls/fetch/rest";import{brenderRegistryBlock as Je,escapeRegExp as We,jsonForScript as Ue,polyglotFallbackLang as U}from"@symbo.ls/utils";let X=null;const ze=async()=>{if(X)return X;try{return X=(await import("@symbo.ls/funcql")).funcqlPlugin,X}catch{return null}};import"linkedom";import{createEarlySeedBlock as Ye}from"@symbo.ls/fetch";import{reset as Ve}from"@symbo.ls/css";const Z=(e,t)=>{if(!e||!t)return;if(e[t]!==void 0)return e[t];const o=t.split(".");let s=e;for(const r of o){if(s==null||typeof s!="object")return;s=s[r]}return s},ne=(e,t)=>U(e?.lang,t),he=(e,t,o,s)=>{if(o?.translations){const n=o.translations[t];if(n){const l=Z(n,e);if(l!==void 0)return l}}if(s?.translations){const n=s.translations[t];if(n){const l=Z(n,e);if(l!==void 0)return l}}const r=U(void 0,o);if(r!==t&&o?.translations){const n=o.translations[r];if(n){const l=Z(n,e);if(l!==void 0)return l}}},Xe=function(e,t){if(!e)return"";const o=this?.context,s=o?.polyglot,r=this?.state?.root||o?.state?.root,n=he(e,t||ne(r,s),s,r);if(n!==void 0)return n;const l=ie(ce);return s&&l&&l.labelKeys.add(e),e},ye=function(){return ne(this?.state?.root,this?.context?.polyglot)},_=(e,t=new WeakMap)=>{if(e===null||typeof e!="object")return e;if(t.has(e))return t.get(e);if(Array.isArray(e)){const s=[];t.set(e,s);for(const r of e)s.push(typeof r=="object"&&r!==null?_(r,t):r);return s}const o={};t.set(e,o);for(const s of Object.keys(e)){const r=e[s];o[s]=typeof r=="object"&&r!==null?_(r,t):r}return o};let $=null;try{import.meta.url&&($=oe(import.meta.url))}catch{}const Ze=()=>{if(!import.meta.url||!$)return{isMonorepo:!1,monorepoRoot:null,resolvePackage:a=>a};const e=Ie(new URL(".",import.meta.url).pathname),t=m(e,"../..");if(b(m(t,"packages","smbls","src","createDomql.js")))return{isMonorepo:!0,monorepoRoot:t};let s;try{const a=$.resolve("smbls/package.json");s=x(a)}catch{let a=e;for(;a!==x(a);){const y=m(a,"node_modules","smbls");if(b(m(y,"package.json"))){s=y;break}a=x(a)}}let r=$;if(s)try{r=oe(m(s,"package.json"))}catch{}let n,l=process.cwd();for(;l!==x(l);){if(b(m(l,"package.json"))){n=l;break}l=x(l)}let u=r;if(n)try{u=oe(m(n,"package.json"))}catch{}return{isMonorepo:!1,smblsRoot:s,brenderDir:e,projectRoot:n,_smblsRequire:r,_projectRequire:u}},P=(e,t)=>{const o=e.isMonorepo?[$]:[e._smblsRequire,e._projectRequire,$].filter(Boolean);for(const s of o)try{return s.resolve(t)}catch{}return null},Qt=(e,t,...o)=>{if(e.isMonorepo)return m(e.monorepoRoot,"packages",t,...o);const s=P(e,`${t}/package.json`);return s?m(x(s),...o):null},eo=(e,t,...o)=>{if(e.isMonorepo)return m(e.monorepoRoot,"plugins",t,...o);const s=P(e,`@symbo.ls/${t}/package.json`);return s?m(x(s),...o):null},re=(e,t,...o)=>{if(e.isMonorepo){for(const r of["packages","plugins"]){const n=m(e.monorepoRoot,r,t,...o);if(b(n))return n}return null}const s=P(e,`@symbo.ls/${t}/package.json`);return s?m(x(s),...o):null},be=(e,t,...o)=>{if(e.isMonorepo)return m(e.monorepoRoot,"packages","domql","packages",t,...o);const s=P(e,`@symbo.ls/${t}/package.json`);return s?m(x(s),...o):null};let Q=null;const Se="smbls/ssr",Qe=async()=>{const e=await import("smbls/ssr");if(typeof e?.createDomqlElement!="function")throw new Error("smbls/ssr does not export createDomqlElement");return e},et=()=>(Q||(Q=tt().catch(e=>{throw Q=null,e})),Q),tt=async()=>{const e=Ze();let t;if($&&e.isMonorepo)t=m(e.monorepoRoot,"packages","smbls","src","createDomql.js");else{let f;try{return await Qe()}catch(c){f=c}if(!$)throw new Error(`brender: ${Se} is not available in this bundled runtime (${f.message}). Bundle smbls \u2265 the release that adds the smbls/ssr export alongside @symbo.ls/brender.`);const i=e.smblsRoot&&m(e.smblsRoot,"src","createDomql.js");if(i&&b(i))t=i;else throw new Error(`brender: cannot load ${Se} (${f.message}). Server rendering needs smbls installed in this project, at a release that exports smbls/ssr`+(e.smblsRoot?` (found smbls at ${e.smblsRoot}).`:" (no smbls package found)."))}const o=await import("esbuild"),s=Ce(Me(),`br_createDomql_${Pe(6).toString("hex")}.mjs`),r=Oe,n=e.isMonorepo?await import(Fe(m(e.monorepoRoot,"scripts","vite-workspace-source.mjs")).href):null,l=n?new Set(n.workspacePackageRoots().map(f=>n.workspacePackageAt(f).name)):null,u=/^(?:smbls(?:\/.*)?|domql|css-in-props(?:\/.*)?|@symbo\.ls\/[^/]+)$/,a={name:"workspace-resolve",setup(f){n&&(f.onResolve({filter:/^[^./]/},i=>{if(u.test(i.path))return;const c=i.path.split("/"),d=i.path.startsWith("@")?c.slice(0,2).join("/"):c[0];if(!l.has(d))return;const p=n.resolveWorkspaceSource(i.path,i.importer||t);return p?{path:p}:{errors:[{text:`"${i.path}" names no source file in the workspace`}]}}),f.onLoad({filter:/[\\/]dist[\\/]/},i=>n.isWorkspaceDist(i.path)?{errors:[{text:`${i.path} is a BUILD of a workspace package \u2014 the render bundles source`}]}:void 0)),f.onResolve({filter:/^smbls/},i=>{const c=i.path.replace(/^smbls\/?/,""),d=e.isMonorepo?m(e.monorepoRoot,"packages","smbls"):e.smblsRoot;if(!d)return;if(!c){const g=r(d);return g?{path:g}:void 0}const p=m(d,c);if(b(p))return{path:p};if(b(p+".js"))return{path:p+".js"};const h=m(p,"index.js");if(b(h))return{path:h}}),f.onResolve({filter:/^domql$/},i=>{if(e.isMonorepo){const c=m(e.monorepoRoot,"packages","domql","src","index.js");if(b(c))return{path:c};const d=m(e.monorepoRoot,"packages","domql","index.js");if(b(d))return{path:d}}else try{const c=_require.resolve("domql/package.json"),d=r(x(c));if(d)return{path:d}}catch{}}),f.onResolve({filter:/^@symbo\.ls\//},i=>{const c=i.path.replace("@symbo.ls/","");if(c==="sync")return{path:"sync-stub",namespace:"brender-stub"};if(e.isMonorepo){for(const p of["packages","plugins"]){const h=m(e.monorepoRoot,p,c,"src","index.js");if(b(h))return{path:h};const g=m(e.monorepoRoot,p,c,"index.js");if(b(g))return{path:g}}const d=m(e.monorepoRoot,"packages","default-config","blank","index.js");if(c==="default-config"&&b(d))return{path:d}}else{const d=re(e,c,"src","index.js");if(d&&b(d))return{path:d};const p=re(e,c,"index.js");if(p&&b(p))return{path:p};if(c==="default-config"){const h=re(e,"default-config","blank","index.js");if(h&&b(h))return{path:h}}}}),f.onResolve({filter:/^@domql\//},i=>{const c=i.path.replace("@symbo.ls/","");if(e.isMonorepo){const d=m(e.monorepoRoot,"packages","domql","packages",c,"src","index.js");if(b(d))return{path:d};const p=m(e.monorepoRoot,"packages","domql","packages",c,"index.js");if(b(p))return{path:p}}else{const d=be(e,c,"src","index.js");if(d&&b(d))return{path:d};const p=be(e,c,"index.js");if(p&&b(p))return{path:p}}}),f.onResolve({filter:/^css-in-props/},i=>{let c;if(e.isMonorepo)c=m(e.monorepoRoot,"packages","css-in-props");else{const h=P(e,"css-in-props/package.json");h&&(c=x(h))}if(!c)return;const d=i.path.replace(/^css-in-props\/?/,"");if(d){const h=m(c,d),g=m(h,"index.js");if(b(g))return{path:g};if(b(h+".js"))return{path:h+".js"};if(b(h))return{path:h}}const p=r(c);if(p)return{path:p}}),f.onResolve({filter:/^@emotion\//},i=>{let c;if(e.isMonorepo)c=m(e.monorepoRoot,"node_modules",i.path);else{const d=P(e,`${i.path}/package.json`);if(!d)return;c=x(d)}if(b(c)){const d=m(c,"package.json");if(b(d))try{const p=JSON.parse(W(d,"utf8")),h=p.module||p.main||"dist/emotion-css.esm.js";return{path:m(c,h)}}catch{}return{path:c}}}),f.onResolve({filter:/\.json$/},i=>{if(i.resolveDir){const c=m(i.resolveDir,i.path);if(b(c))return{path:c}}}),f.onLoad({filter:/smbls\/.*options\.js$/},async i=>{if(!i.path.includes("smbls/")||!i.path.endsWith("options.js"))return;let c=W(i.path,"utf8");return c=c.replace(/import\s*\{[^}]*version[^}]*\}\s*from\s*['"][^'"]*package\.json['"][^;\n]*/,"const version = '0.0.0'"),c=c.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:c,loader:"js"}}),f.onLoad({filter:/smbls\/.*init\.js$/},async i=>{if(!i.path.includes("smbls/")||!i.path.endsWith("init.js"))return;let c=W(i.path,"utf8");return c=c.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:c,loader:"js"}}),f.onLoad({filter:/.*/,namespace:"brender-stub"},()=>({contents:"export const SyncComponent = {}; export const Inspect = {}; export const Notifications = {}; export const syncPlugin = () => ({}); export const notificationsPlugin = () => ({}); export const seedRawDesignSystem = () => {}; export const inspectPlugin = () => ({}); export default {}",loader:"js"})),f.onLoad({filter:/smbls\/src\/router\.js$/},async i=>{let c=W(i.path,"utf8");return c=c.replace(/import\s*\{\s*Link\s*\}\s*from\s*['"]smbls['"]/,"const Link = { tag: 'a', attr: { href: (el) => el.href } }"),{contents:c,loader:"js"}}),f.onLoad({filter:/fetchOnCreate\.js$/},async i=>{if(!i.path.includes("smbls/"))return;let c=W(i.path,"utf8");return c=c.replace(/window\s*&&\s*window\.location\s*\?\s*window\.location\.host\.includes/g,"window && window.location && window.location.host ? window.location.host.includes"),{contents:c,loader:"js"}})}};await o.build({entryPoints:[t],bundle:!0,format:"esm",platform:"node",outfile:s,write:!0,logLevel:"warning",plugins:[a],nodePaths:e.isMonorepo?[m(e.monorepoRoot,"node_modules")]:[...e.smblsRoot?[m(e.smblsRoot,"node_modules")]:[],...e.projectRoot?[m(e.projectRoot,"node_modules")]:[],...e.smblsRoot?[m(e.smblsRoot,"..","..","node_modules")]:[]].filter(f=>b(f)),supported:{"import-attributes":!1},external:["fs","path","os","crypto","url","http","https","stream","util","events","buffer","child_process","worker_threads","net","tls","dns","dgram","zlib","assert","querystring","string_decoder","readline","perf_hooks","async_hooks","v8","vm","cluster","inspector","module","process","tty","color-contrast-checker","linkedom"]});const y=await import(`file://${s}`);if(process.env.BRENDER_DEBUG)console.log("[brender] Bundle saved:",s);else try{De(s)}catch{}return y},ot={Box:{},Focusable:{},Block:{display:"block"},Inline:{display:"inline"},Flex:{display:"flex"},InlineFlex:{display:"inline-flex"},Grid:{display:"grid"},InlineGrid:{display:"inline-grid"},Link:{tag:"a",attr:{href:e=>e.href,target:e=>e.target,rel:e=>e.rel}},A:{extends:"Link"},RouteLink:{extends:"Link"},Img:{tag:"img",attr:{src:e=>{let t=e.src;return typeof t=="string"&&t.includes("{{")&&(t=e.call("replaceLiteralsWithObjectFields",t,e.state)),t},alt:e=>e.alt,loading:e=>e.loading}},Image:{extends:"Img"},Button:{tag:"button"},FocusableComponent:{tag:"button"},Form:{tag:"form"},Input:{tag:"input"},TextArea:{tag:"textarea"},Textarea:{tag:"textarea"},Select:{tag:"select"},Label:{tag:"label"},Iframe:{tag:"iframe"},Video:{tag:"video"},Audio:{tag:"audio"},Canvas:{tag:"canvas"},Span:{tag:"span"},P:{tag:"p"},H1:{tag:"h1"},H2:{tag:"h2"},H3:{tag:"h3"},H4:{tag:"h4"},H5:{tag:"h5"},H6:{tag:"h6"},Svg:{tag:"svg",attr:{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"}},Text:{tag:"span"}},st=(e,t)=>{const o={};return ke(e,(s,r)=>{if(!r.__brKey)return;const n=t(s);o[n===""?"__root":n]=r.__brKey}),o},ke=(e,t)=>{const o=s=>{const r=s&&s.__ref;if(r&&(t(s,r),r.__children))for(const n of r.__children)o(s[n])};o(e)},nt=new Set(["app","config","pages","components","state","snippets","designSystem","dependencies","functions","methods","files","sharedLibraries"]);let F=0;const Re=Symbol.for("brender.fetchTracked"),we=()=>F,rt=256*1024,ct=1024*1024,it=new Set(["accept-language","accept"]),xe={renders:0,record:null},lt=50,ce={renders:0,record:null},je=(e,t)=>{e.renders++,e.renders===1?e.record=t():e.record&&(e.record.tainted=!0);const o=e.record;return{record:o,close:()=>{e.renders--,e.record===o&&(e.record=null)}}},ie=e=>e.record&&!e.record.tainted?e.record:null,at=e=>{try{const t=new URL(e);return t.origin+t.pathname}catch{return String(e).split("?")[0]}},le=(e,t,o)=>{!e||e.tainted||e.failures.length>=lt||e.failures.push({url:at(t),reason:o})},ve=e=>e&&typeof e=="object"&&typeof e.url=="string"?e.url:String(e),ft=e=>{const[t,o]=e,s=t&&typeof t=="object"&&typeof t.url=="string",r=ve(t),n=String(o&&o.method||s&&t.method||"GET").toUpperCase(),l={},u=a=>{if(!a)return;const y=!Array.isArray(a)&&typeof a.entries!="function";for(const[f,i]of He(a))y&&i==null||(l[String(f).toLowerCase()]=String(i))};return s&&u(t.headers),o&&u(o.headers),{url:r,method:n,headers:l}},pt=(e,t,o,s)=>{if(!o||!o.ok||typeof o.clone!="function")return;const r=o.headers&&o.headers.get&&o.headers.get("content-type")||"";if(!/json|^text\//i.test(r))return;let n;try{n=o.clone()}catch{return}s(n.text().then(l=>{e.tainted||l.length>rt||e.bytes+l.length>ct||(e.bytes+=l.length,e.entries.push({url:t.url,lang:t.headers["accept-language"]||"",status:o.status,type:r,body:l}))},()=>{}))},ut=()=>{const e=globalThis.fetch;if(typeof e!="function"||e[Re])return;const t=function(...o){F++;let s=!1;const r=()=>{s||(s=!0,F--)},n=i=>{F++,i.then(()=>{F--},()=>{F--})},l=ie(xe),u=ie(ce);let a=null;if(l)try{a=ft(o),(a.method!=="GET"||!/^https?:/i.test(a.url)||Object.keys(a.headers).some(i=>!it.has(i)))&&(a=null)}catch{a=null}const y=()=>ve(o[0]);let f;try{f=e.apply(this,o)}catch(i){throw le(u,y(),i&&i.message||"failed"),r(),i}return f&&typeof f.then=="function"?f.then(i=>{a&&pt(l,a,i,n),u&&i&&i.ok===!1&&le(u,y(),"HTTP "+i.status),r()},i=>{le(u,y(),i&&i.message||"network error"),r()}):r(),f};t[Re]=!0,globalThis.fetch=t},to=({brRegistry:e,fetchSeed:t,hydrateFlag:o=!0}={})=>{if(!e||!Object.keys(e).length)return"";const s=Ue(e);return o?`<script>window.__BR_REGISTRY__=${s};window.__BRENDER_HYDRATE__=true<\/script>`+(t&&t.length?`
2
- `+Ge(t):""):`<script>window.__BR_REGISTRY__=${s}<\/script>`},_e=async(e,t={})=>{const{route:o="/",pathname:s,state:r,context:n,prefetch:l=!1}=t,u=s||o;ut();const a=je(ce,()=>({failures:[],labelKeys:new Set,tainted:!1}));try{return await dt(e,{route:o,locationPath:u,stateOverrides:r,contextOverrides:n,prefetch:l},a.record)}finally{a.close()}},dt=async(e,{route:t,locationPath:o,stateOverrides:s,contextOverrides:r,prefetch:n},l)=>{let u;if(n)try{u=await Ne(e)}catch(c){console.warn("[brender] SSR translation fetch failed:",c.message)}const{window:a,document:y}=ue(),f=y.body;a.location.pathname=o;const i=[ee("document",y),ee("location",a.location),ee("localStorage",a.localStorage),ee("sessionStorage",a.sessionStorage)];try{const{createDomqlElement:c}=await et(),d=_(e.app||{}),p={...e.config||{}};e.polyglot&&!p.polyglot&&(p.polyglot=e.polyglot),e.fetch&&!p.fetch&&(p.fetch=e.fetch),e.router&&!p.router&&(p.router=e.router);for(const S of bt)e[S]!=null&&p[S]==null&&(p[S]=e[S]);const h=p.polyglot?{...p.polyglot}:void 0;u&&h&&(h.translations={...h.translations||{},...u});const g=_(e.state||{});(u||h)&&(g.root||(g.root={}),h&&(g.root.lang=U(g.root.lang,h)),u&&(g.root.translations={...g.root.translations||{},...u})),Ve();const q={};for(const S in e){if(nt.has(S))continue;const R=e[S];q[S]=R&&typeof R=="object"?_(R):R}const T={...q,state:g,...s?{state:{...g,...s}}:{},dependencies:_(e.dependencies||{}),components:_(e.components||{}),snippets:_(e.snippets||{}),pages:_(e.pages||{}),functions:{...e.functions||{},polyglot:Xe,getActiveLang:ye,getLang:ye},methods:e.methods||{},designSystem:_(e.designSystem||{}),files:e.files||{},sharedLibraries:e.sharedLibraries||[],...p,...h?{polyglot:h}:{},document:y,window:a,parent:{node:f},initOptions:{},domqlOptions:{sourcemap:!1},firstRender:!1,sync:!1,inspect:!1,notifications:!1,isolateDesignSystem:!0,isolateFetchCache:!0,...r||{}},B=await ze();B&&(T.plugins=T.plugins||[],T.plugins.some(S=>S.name==="funcql")||T.plugins.push(B)),de();const I=je(xe,()=>({entries:[],bytes:0,tainted:!1})),O=I.record;let j;try{j=await c(d,T);const S=Date.now(),R=n?2e3:50,L=M=>new Promise(J=>setTimeout(J,M));for(await L(Math.min(50,R));n&&we()>0&&Date.now()-S<R;)await L(10),we()===0&&await L(Math.max(0,Math.min(50,R-(Date.now()-S))))}finally{I.close()}const z=O&&!O.tainted?O.entries.slice():[];me(f);const Y=ge(j),{elementPath:A}=await import("@symbo.ls/element"),K=st(j,A),N=se(e,t,j,j?.state),v=[],C=y.head||y.querySelector("head");if(C)for(const S of C.querySelectorAll("style")){if(S.sheet&&S.sheet.cssRules)for(const R of S.sheet.cssRules)R.cssText&&v.push(R);if(!v.length){const R=S.textContent||"";R&&v.push({cssText:R})}}const D=v.map(S=>S.cssText),{globalCSS:V,classRules:pe}=wt(v);let k=Le(f.innerHTML);const w=j?.state?.root,E=ne(w,h),G=mt(u,w);G&&(k=Te(k,Ee(G,E,h)));const H=l&&!l.tainted?{failedRequests:l.failures.slice(),rawLabelKeys:yt(l.labelKeys,k,E,h,w)}:null;return{html:k,metadata:N,registry:Y,brRegistry:K,element:j,emotionCSS:D,globalCSS:V,classRules:pe,fetchSeed:z,document:y,window:a,ssrTranslations:G||void 0,activeLang:E,diagnostics:H}}finally{for(const c of i)c()}},ee=(e,t)=>{const o=Object.getOwnPropertyDescriptor(globalThis,e);return o&&!o.configurable?!o.writable&&!o.set?()=>{}:(globalThis[e]=t,()=>{}):(Object.defineProperty(globalThis,e,{value:t,writable:!0,configurable:!0,enumerable:!0}),()=>{o?Object.defineProperty(globalThis,e,o):delete globalThis[e]})},ae=/\{\{\s*([^|{}]+?)\s*\|\s*polyglot\s*\}\}/g,fe=e=>e&&typeof e.parse=="function"?e.parse():e,mt=(e,t)=>{const o=fe(t?.translations),s={};for(const r of[e,o])if(!(!r||typeof r!="object"))for(const n in r){const l=fe(r[n]);l&&typeof l=="object"&&(s[n]={...s[n]||{},...l})}return Object.keys(s).length?s:null},Ee=(e,t,o)=>e[t]||e[U(void 0,o)]||Object.values(e)[0]||{},Te=(e,t)=>e.replace(ae,(o,s)=>Z(t,s.trim())??o),gt=/<script\b[^>]*\btype\s*=\s*["']?application\/json["']?[^>]*>[\s\S]*?<\/script\s*>/gi,ht=(e,t)=>new RegExp(`(?:^|[^\\w.-])${We(t)}(?![\\w-]|\\.\\w)`).test(e),yt=(e,t,o,s,r)=>{const n=new Set,l=t.replace(gt,"");for(const u of e)/\s/.test(u)||he(u,o,s,r)===void 0&&ht(l,u)&&n.add(u);for(const u of l.matchAll(ae))n.add(u[1].trim());return[...n]},oo=async(e,t={})=>{const{context:o={}}=t,{window:s,document:r}=ue(),n=r.body,{create:l}=await import("@symbo.ls/element"),u=await import("@symbo.ls/utils"),a={...ot,...o.components||{}},y={...u,...o.utils||{},...o.functions||{}};de();let f;try{f=l(e,{node:n},"root",{context:{document:r,window:s,...o,components:a,utils:y}})}catch{}me(n);const i=f?ge(f):{};return{html:Le(n.innerHTML),registry:i,element:f}},Le=e=>e.replace(/(<svg\b[^>]*>)([\s\S]*?)(<\/svg>)/gi,(t,o,s,r)=>{if(s.includes("&lt;")){const n=s.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&#39;/g,"'");return o+n+r}return t}),bt=["globalTheme","themeStorageKey","themeRoot","useReset","useVariable","useFontImport","useIconSprite","useSvgSprite","useDocumentTheme","useDefaultConfig","useDefaultIcons","useThemeSuffixedVars","verbose","semanticIcons"],St=/\.[A-Za-z_\\-]/,kt=e=>{if(typeof e!="string"||!e)return!1;const t=e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,'""').replace(/\[[^\]]*\]/g,"[]");return!St.test(t)},Rt=/^\s*(:root|\[data-theme|\[data-skin)/,$e=e=>{const t=e&&e.cssText;if(typeof t!="string")return null;if(/^@(-webkit-)?keyframes\b/.test(t))return"keyframes";if(t.startsWith("@font-face"))return null;if(typeof e.selectorText=="string")return kt(e.selectorText)?Rt.test(e.selectorText)?"root":"reset":null;const o=e.cssRules&&Array.from(e.cssRules);if(o&&o.length&&/^@(media|supports|container)\b/.test(t)){const s=new Set(o.map($e));return s.has(null)||s.size!==1?s.has(null)?null:"reset":[...s][0]}return null},wt=e=>{const t={root:[],reset:[],keyframes:[]},o=[];for(const s of e||[]){const r=$e(s);r?t[r].push(s.cssText):s&&s.cssText&&o.push(s.cssText)}return{globalCSS:{rootRule:t.root.join(`
1
+ import{resolve as u,join as Fe,dirname as j}from"path";import{existsSync as b,unlinkSync as Be,readFileSync as J,realpathSync as Ge}from"fs";import{resolvePackageEntry as Ke}from"./packageEntry.js";import{tmpdir as Ne}from"os";import{randomBytes as He}from"crypto";import{createRequire as oe}from"module";import{pathToFileURL as Ue}from"url";import{createEnv as ue}from"./env.js";import{resetKeys as de,assignKeys as me,mapKeysToElements as ge}from"./keys.js";import{extractMetadata as ne,generateHeadHtml as We}from"./metadata.js";import"./hydrate.js";import{composeIntoShell as Je}from"./shell.js";import{fetchSSRTranslations as ze}from"./prefetch.js";import{createEarlySeedScript as Ye}from"@symbo.ls/fetch";import{headerEntries as Xe}from"@symbo.ls/fetch/rest";import{brenderRegistryBlock as Ve,escapeRegExp as Qe,jsonForScript as Ze,polyglotFallbackLang as z,polyglotLangStorageKey as et,polyglotAddressLang as tt,polyglotStartupScript as ot}from"@symbo.ls/utils";let Y=null;const nt=async()=>{if(Y)return Y;try{return Y=(await import("@symbo.ls/funcql")).funcqlPlugin,Y}catch{return null}};import"linkedom";import{createEarlySeedBlock as st}from"@symbo.ls/fetch";import{reset as rt}from"@symbo.ls/css";const X=(e,t)=>{if(!e||!t)return;if(e[t]!==void 0)return e[t];const o=t.split(".");let n=e;for(const s of o){if(n==null||typeof n!="object")return;n=n[s]}return n},se=(e,t)=>z(e?.lang,t),he=(e,t,o,n)=>{if(o?.translations){const r=o.translations[t];if(r){const i=X(r,e);if(i!==void 0)return i}}if(n?.translations){const r=n.translations[t];if(r){const i=X(r,e);if(i!==void 0)return i}}const s=z(void 0,o);if(s!==t&&o?.translations){const r=o.translations[s];if(r){const i=X(r,e);if(i!==void 0)return i}}},ct=function(e,t){if(!e)return"";const o=this?.context,n=o?.polyglot,s=this?.state?.root||o?.state?.root,r=he(e,t||se(s,n),n,s);if(r!==void 0)return r;const i=ie(ce);return n&&i&&i.labelKeys.add(e),e},ye=function(){return se(this?.state?.root,this?.context?.polyglot)},L=(e,t=new WeakMap)=>{if(e===null||typeof e!="object")return e;if(t.has(e))return t.get(e);if(Array.isArray(e)){const n=[];t.set(e,n);for(const s of e)n.push(typeof s=="object"&&s!==null?L(s,t):s);return n}const o={};t.set(e,o);for(const n of Object.keys(e)){const s=e[n];o[n]=typeof s=="object"&&s!==null?L(s,t):s}return o};let A=null;try{import.meta.url&&(A=oe(import.meta.url))}catch{}const it=()=>{if(!import.meta.url||!A)return{isMonorepo:!1,monorepoRoot:null,resolvePackage:l=>l};const e=Ge(new URL(".",import.meta.url).pathname),t=u(e,"../..");if(b(u(t,"packages","smbls","src","createDomql.js")))return{isMonorepo:!0,monorepoRoot:t};let n;try{const l=A.resolve("smbls/package.json");n=j(l)}catch{let l=e;for(;l!==j(l);){const g=u(l,"node_modules","smbls");if(b(u(g,"package.json"))){n=g;break}l=j(l)}}let s=A;if(n)try{s=oe(u(n,"package.json"))}catch{}let r,i=process.cwd();for(;i!==j(i);){if(b(u(i,"package.json"))){r=i;break}i=j(i)}let d=s;if(r)try{d=oe(u(r,"package.json"))}catch{}return{isMonorepo:!1,smblsRoot:n,brenderDir:e,projectRoot:r,_smblsRequire:s,_projectRequire:d}},O=(e,t)=>{const o=e.isMonorepo?[A]:[e._smblsRequire,e._projectRequire,A].filter(Boolean);for(const n of o)try{return n.resolve(t)}catch{}return null},yo=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"packages",t,...o);const n=O(e,`${t}/package.json`);return n?u(j(n),...o):null},bo=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"plugins",t,...o);const n=O(e,`@symbo.ls/${t}/package.json`);return n?u(j(n),...o):null},re=(e,t,...o)=>{if(e.isMonorepo){for(const s of["packages","plugins"]){const r=u(e.monorepoRoot,s,t,...o);if(b(r))return r}return null}const n=O(e,`@symbo.ls/${t}/package.json`);return n?u(j(n),...o):null},be=(e,t,...o)=>{if(e.isMonorepo)return u(e.monorepoRoot,"packages","domql","packages",t,...o);const n=O(e,`@symbo.ls/${t}/package.json`);return n?u(j(n),...o):null};let V=null;const Se="smbls/ssr",at=async()=>{const e=await import("smbls/ssr");if(typeof e?.createDomqlElement!="function")throw new Error("smbls/ssr does not export createDomqlElement");return e},ke=()=>(V||(V=lt().catch(e=>{throw V=null,e})),V),lt=async()=>{const e=it();let t;if(A&&e.isMonorepo)t=u(e.monorepoRoot,"packages","smbls","src","createDomql.js");else{let f;try{return await at()}catch(a){f=a}if(!A)throw new Error(`brender: ${Se} is not available in this bundled runtime (${f.message}). Bundle smbls \u2265 the release that adds the smbls/ssr export alongside @symbo.ls/brender.`);const c=e.smblsRoot&&u(e.smblsRoot,"src","createDomql.js");if(c&&b(c))t=c;else throw new Error(`brender: cannot load ${Se} (${f.message}). Server rendering needs smbls installed in this project, at a release that exports smbls/ssr`+(e.smblsRoot?` (found smbls at ${e.smblsRoot}).`:" (no smbls package found)."))}const o=await import("esbuild"),n=Fe(Ne(),`br_createDomql_${He(6).toString("hex")}.mjs`),s=Ke,r=e.isMonorepo?await import(Ue(u(e.monorepoRoot,"scripts","vite-workspace-source.mjs")).href):null,i=r?new Set(r.workspacePackageRoots().map(f=>r.workspacePackageAt(f).name)):null,d=/^(?:smbls(?:\/.*)?|domql|css-in-props(?:\/.*)?|@symbo\.ls\/[^/]+)$/,l={name:"workspace-resolve",setup(f){r&&(f.onResolve({filter:/^[^./]/},c=>{if(d.test(c.path))return;const a=c.path.split("/"),p=c.path.startsWith("@")?a.slice(0,2).join("/"):a[0];if(!i.has(p))return;const m=r.resolveWorkspaceSource(c.path,c.importer||t);return m?{path:m}:{errors:[{text:`"${c.path}" names no source file in the workspace`}]}}),f.onLoad({filter:/[\\/]dist[\\/]/},c=>r.isWorkspaceDist(c.path)?{errors:[{text:`${c.path} is a BUILD of a workspace package \u2014 the render bundles source`}]}:void 0)),f.onResolve({filter:/^smbls/},c=>{const a=c.path.replace(/^smbls\/?/,""),p=e.isMonorepo?u(e.monorepoRoot,"packages","smbls"):e.smblsRoot;if(!p)return;if(!a){const S=s(p);return S?{path:S}:void 0}const m=u(p,a);if(b(m))return{path:m};if(b(m+".js"))return{path:m+".js"};const y=u(m,"index.js");if(b(y))return{path:y}}),f.onResolve({filter:/^domql$/},c=>{if(e.isMonorepo){const a=u(e.monorepoRoot,"packages","domql","src","index.js");if(b(a))return{path:a};const p=u(e.monorepoRoot,"packages","domql","index.js");if(b(p))return{path:p}}else try{const a=_require.resolve("domql/package.json"),p=s(j(a));if(p)return{path:p}}catch{}}),f.onResolve({filter:/^@symbo\.ls\//},c=>{const a=c.path.replace("@symbo.ls/","");if(a==="sync")return{path:"sync-stub",namespace:"brender-stub"};if(e.isMonorepo){for(const m of["packages","plugins"]){const y=u(e.monorepoRoot,m,a,"src","index.js");if(b(y))return{path:y};const S=u(e.monorepoRoot,m,a,"index.js");if(b(S))return{path:S}}const p=u(e.monorepoRoot,"packages","default-config","blank","index.js");if(a==="default-config"&&b(p))return{path:p}}else{const p=re(e,a,"src","index.js");if(p&&b(p))return{path:p};const m=re(e,a,"index.js");if(m&&b(m))return{path:m};if(a==="default-config"){const y=re(e,"default-config","blank","index.js");if(y&&b(y))return{path:y}}}}),f.onResolve({filter:/^@domql\//},c=>{const a=c.path.replace("@symbo.ls/","");if(e.isMonorepo){const p=u(e.monorepoRoot,"packages","domql","packages",a,"src","index.js");if(b(p))return{path:p};const m=u(e.monorepoRoot,"packages","domql","packages",a,"index.js");if(b(m))return{path:m}}else{const p=be(e,a,"src","index.js");if(p&&b(p))return{path:p};const m=be(e,a,"index.js");if(m&&b(m))return{path:m}}}),f.onResolve({filter:/^css-in-props/},c=>{let a;if(e.isMonorepo)a=u(e.monorepoRoot,"packages","css-in-props");else{const y=O(e,"css-in-props/package.json");y&&(a=j(y))}if(!a)return;const p=c.path.replace(/^css-in-props\/?/,"");if(p){const y=u(a,p),S=u(y,"index.js");if(b(S))return{path:S};if(b(y+".js"))return{path:y+".js"};if(b(y))return{path:y}}const m=s(a);if(m)return{path:m}}),f.onResolve({filter:/^@emotion\//},c=>{let a;if(e.isMonorepo)a=u(e.monorepoRoot,"node_modules",c.path);else{const p=O(e,`${c.path}/package.json`);if(!p)return;a=j(p)}if(b(a)){const p=u(a,"package.json");if(b(p))try{const m=JSON.parse(J(p,"utf8")),y=m.module||m.main||"dist/emotion-css.esm.js";return{path:u(a,y)}}catch{}return{path:a}}}),f.onResolve({filter:/\.json$/},c=>{if(c.resolveDir){const a=u(c.resolveDir,c.path);if(b(a))return{path:a}}}),f.onLoad({filter:/smbls\/.*options\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("options.js"))return;let a=J(c.path,"utf8");return a=a.replace(/import\s*\{[^}]*version[^}]*\}\s*from\s*['"][^'"]*package\.json['"][^;\n]*/,"const version = '0.0.0'"),a=a.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:a,loader:"js"}}),f.onLoad({filter:/smbls\/.*init\.js$/},async c=>{if(!c.path.includes("smbls/")||!c.path.endsWith("init.js"))return;let a=J(c.path,"utf8");return a=a.replace(/import\s*\{[^}]*createRequire[^}]*\}\s*from\s*['"]module['"][^;\n]*/g,"// createRequire removed for brender build"),{contents:a,loader:"js"}}),f.onLoad({filter:/.*/,namespace:"brender-stub"},()=>({contents:"export const SyncComponent = {}; export const Inspect = {}; export const Notifications = {}; export const syncPlugin = () => ({}); export const notificationsPlugin = () => ({}); export const seedRawDesignSystem = () => {}; export const inspectPlugin = () => ({}); export default {}",loader:"js"})),f.onLoad({filter:/smbls\/src\/router\.js$/},async c=>{let a=J(c.path,"utf8");return a=a.replace(/import\s*\{\s*Link\s*\}\s*from\s*['"]smbls['"]/,"const Link = { tag: 'a', attr: { href: (el) => el.href } }"),{contents:a,loader:"js"}}),f.onLoad({filter:/fetchOnCreate\.js$/},async c=>{if(!c.path.includes("smbls/"))return;let a=J(c.path,"utf8");return a=a.replace(/window\s*&&\s*window\.location\s*\?\s*window\.location\.host\.includes/g,"window && window.location && window.location.host ? window.location.host.includes"),{contents:a,loader:"js"}})}};await o.build({entryPoints:[t],bundle:!0,format:"esm",platform:"node",outfile:n,write:!0,logLevel:"warning",plugins:[l],nodePaths:e.isMonorepo?[u(e.monorepoRoot,"node_modules")]:[...e.smblsRoot?[u(e.smblsRoot,"node_modules")]:[],...e.projectRoot?[u(e.projectRoot,"node_modules")]:[],...e.smblsRoot?[u(e.smblsRoot,"..","..","node_modules")]:[]].filter(f=>b(f)),supported:{"import-attributes":!1},external:["fs","path","os","crypto","url","http","https","stream","util","events","buffer","child_process","worker_threads","net","tls","dns","dgram","zlib","assert","querystring","string_decoder","readline","perf_hooks","async_hooks","v8","vm","cluster","inspector","module","process","tty","color-contrast-checker","linkedom"]});const g=await import(`file://${n}`);if(process.env.BRENDER_DEBUG)console.log("[brender] Bundle saved:",n);else try{Be(n)}catch{}return g},ft={Box:{},Focusable:{},Block:{display:"block"},Inline:{display:"inline"},Flex:{display:"flex"},InlineFlex:{display:"inline-flex"},Grid:{display:"grid"},InlineGrid:{display:"inline-grid"},Link:{tag:"a",attr:{href:e=>e.href,target:e=>e.target,rel:e=>e.rel}},A:{extends:"Link"},RouteLink:{extends:"Link"},Img:{tag:"img",attr:{src:e=>{let t=e.src;return typeof t=="string"&&t.includes("{{")&&(t=e.call("replaceLiteralsWithObjectFields",t,e.state)),t},alt:e=>e.alt,loading:e=>e.loading}},Image:{extends:"Img"},Button:{tag:"button"},FocusableComponent:{tag:"button"},Form:{tag:"form"},Input:{tag:"input"},TextArea:{tag:"textarea"},Textarea:{tag:"textarea"},Select:{tag:"select"},Label:{tag:"label"},Iframe:{tag:"iframe"},Video:{tag:"video"},Audio:{tag:"audio"},Canvas:{tag:"canvas"},Span:{tag:"span"},P:{tag:"p"},H1:{tag:"h1"},H2:{tag:"h2"},H3:{tag:"h3"},H4:{tag:"h4"},H5:{tag:"h5"},H6:{tag:"h6"},Svg:{tag:"svg",attr:{xmlns:"http://www.w3.org/2000/svg","xmlns:xlink":"http://www.w3.org/1999/xlink"}},Text:{tag:"span"}},pt=(e,t)=>{const o={};return Re(e,(n,s)=>{if(!s.__brKey)return;const r=t(n);o[r===""?"__root":r]=s.__brKey}),o},Re=(e,t)=>{const o=n=>{const s=n&&n.__ref;if(s&&(t(n,s),s.__children))for(const r of s.__children)o(n[r])};o(e)},ut=new Set(["app","config","pages","components","state","snippets","designSystem","dependencies","functions","methods","files","sharedLibraries"]);let P=0;const we=Symbol.for("brender.fetchTracked"),xe=()=>P,dt=256*1024,mt=1024*1024,gt=new Set(["accept-language","accept"]),je={renders:0,record:null},ht=50,ce={renders:0,record:null},_e=(e,t)=>{e.renders++,e.renders===1?e.record=t():e.record&&(e.record.tainted=!0);const o=e.record;return{record:o,close:()=>{e.renders--,e.record===o&&(e.record=null)}}},ie=e=>e.record&&!e.record.tainted?e.record:null,yt=e=>{try{const t=new URL(e);return t.origin+t.pathname}catch{return String(e).split("?")[0]}},ae=(e,t,o)=>{!e||e.tainted||e.failures.length>=ht||e.failures.push({url:yt(t),reason:o})},Ee=e=>e&&typeof e=="object"&&typeof e.url=="string"?e.url:String(e),bt=e=>{const[t,o]=e,n=t&&typeof t=="object"&&typeof t.url=="string",s=Ee(t),r=String(o&&o.method||n&&t.method||"GET").toUpperCase(),i={},d=l=>{if(!l)return;const g=!Array.isArray(l)&&typeof l.entries!="function";for(const[f,c]of Xe(l))g&&c==null||(i[String(f).toLowerCase()]=String(c))};return n&&d(t.headers),o&&d(o.headers),{url:s,method:r,headers:i}},St=(e,t,o,n)=>{if(!o||!o.ok||typeof o.clone!="function")return;const s=o.headers&&o.headers.get&&o.headers.get("content-type")||"";if(!/json|^text\//i.test(s))return;let r;try{r=o.clone()}catch{return}n(r.text().then(i=>{e.tainted||i.length>dt||e.bytes+i.length>mt||(e.bytes+=i.length,e.entries.push({url:t.url,lang:t.headers["accept-language"]||"",status:o.status,type:s,body:i}))},()=>{}))},kt=()=>{const e=globalThis.fetch;if(typeof e!="function"||e[we])return;const t=function(...o){P++;let n=!1;const s=()=>{n||(n=!0,P--)},r=c=>{P++,c.then(()=>{P--},()=>{P--})},i=ie(je),d=ie(ce);let l=null;if(i)try{l=bt(o),(l.method!=="GET"||!/^https?:/i.test(l.url)||Object.keys(l.headers).some(c=>!gt.has(c)))&&(l=null)}catch{l=null}const g=()=>Ee(o[0]);let f;try{f=e.apply(this,o)}catch(c){throw ae(d,g(),c&&c.message||"failed"),s(),c}return f&&typeof f.then=="function"?f.then(c=>{l&&St(i,l,c,r),d&&c&&c.ok===!1&&ae(d,g(),"HTTP "+c.status),s()},c=>{ae(d,g(),c&&c.message||"network error"),s()}):s(),f};t[we]=!0,globalThis.fetch=t},So=({brRegistry:e,fetchSeed:t,hydrateFlag:o=!0}={})=>{if(!e||!Object.keys(e).length)return"";const n=Ze(e);return o?`<script>window.__BR_REGISTRY__=${n};window.__BRENDER_HYDRATE__=true<\/script>`+(t&&t.length?`
2
+ `+Ye(t):""):`<script>window.__BR_REGISTRY__=${n}<\/script>`},Rt=3e4,wt=2147483647;let ve=Promise.resolve();const Te=()=>{},xt=e=>{const t=ve.then(e);return ve=t.then(Te,Te),t},jt=e=>{if(e===void 0)return Rt;if(e===0||e===!1||e===1/0)return 0;if(typeof e!="number"||!(e>0))throw new TypeError(`brender: options.timeout is a number of milliseconds, or 0 / false for no limit \u2014 not ${JSON.stringify(e)}`);return Math.min(e,wt)},_t=(e,t)=>{const o=e.route||"/",n=e.pathname||o,s=n===o?o:`${n} (route ${o})`,r=new Error(`brender: the render of ${s} did not finish within ${t} ms and was abandoned, so the renders after it can run (options.timeout)`);return r.code="BRENDER_RENDER_TIMEOUT",r.route=o,r.pathname=n,r.timeout=t,r},Et=e=>{e.closed=!0;for(const t of e.undo.splice(0))t()},vt=e=>{let t=!1;return()=>{t||(t=!0,e())}},Le=(e,t={})=>{let o;try{o=jt(t.timeout)}catch(n){return Promise.reject(n)}return xt(()=>Tt(e,t,o))},Tt=async(e,t,o)=>{await ke();const n={closed:!1,undo:[]},s=Lt(e,t,n);if(!o)return s;let r;const i=new Promise((d,l)=>{r=setTimeout(()=>{Et(n),l(_t(t,o))},o)});try{return await Promise.race([s,i])}finally{clearTimeout(r)}},Lt=async(e,t,o)=>{const{route:n="/",pathname:s,state:r,context:i,prefetch:d=!1}=t,l=s||n;kt();const g=_e(ce,()=>({failures:[],labelKeys:new Set,tainted:!1}));try{return await $t(e,{route:n,locationPath:l,stateOverrides:r,contextOverrides:i,prefetch:d},g.record,o)}finally{g.close()}},$t=async(e,{route:t,locationPath:o,stateOverrides:n,contextOverrides:s,prefetch:r},i,d)=>{const l=()=>{if(d.closed)throw new Error("brender: this render was abandoned at its timeout")};let g;if(r){try{g=await ze(e)}catch(m){d.closed||console.warn("[brender] SSR translation fetch failed:",m.message)}l()}const{window:f,document:c}=ue(),a=c.body;f.location.pathname=o;const p=[Q("document",c),Q("location",f.location),Q("localStorage",f.localStorage),Q("sessionStorage",f.sessionStorage)].map(vt);d.undo.push(...p);try{const{createDomqlElement:m}=await ke();l();const y=L(e.app||{}),S={...e.config||{}};e.polyglot&&!S.polyglot&&(S.polyglot=e.polyglot),e.fetch&&!S.fetch&&(S.fetch=e.fetch),e.router&&!S.router&&(S.router=e.router);for(const k of Mt)e[k]!=null&&S[k]==null&&(S[k]=e[k]);const h=S.polyglot?{...S.polyglot}:void 0,q=tt(o,h);q&&f.localStorage.setItem(et(h),q),g&&h&&(h.translations={...h.translations||{},...g});const _=L(e.state||{});(g||h)&&(_.root||(_.root={}),h&&(_.root.lang=z(_.root.lang,h)),g&&(_.root.translations={..._.root.translations||{},...g})),rt();const F={};for(const k in e){if(ut.has(k))continue;const w=e[k];F[k]=w&&typeof w=="object"?L(w):w}const $={...F,state:_,...n?{state:{..._,...n}}:{},dependencies:L(e.dependencies||{}),components:L(e.components||{}),snippets:L(e.snippets||{}),pages:L(e.pages||{}),functions:{...e.functions||{},polyglot:ct,getActiveLang:ye,getLang:ye},methods:e.methods||{},designSystem:L(e.designSystem||{}),files:e.files||{},sharedLibraries:e.sharedLibraries||[],...S,...h?{polyglot:h}:{},document:c,window:f,parent:{node:a},initOptions:{},domqlOptions:{sourcemap:!1},firstRender:!1,sync:!1,inspect:!1,notifications:!1,isolateDesignSystem:!0,isolateFetchCache:!0,...s||{}},B=await nt();l(),B&&($.plugins=$.plugins||[],$.plugins.some(k=>k.name==="funcql")||$.plugins.push(B)),de();const G=_e(je,()=>({entries:[],bytes:0,tainted:!1})),D=G.record;let v;try{v=await m(y,$),l();const k=Date.now(),w=r?2e3:50,te=Oe=>new Promise(Pe=>setTimeout(Pe,Oe));for(await te(Math.min(50,w));r&&!d.closed&&xe()>0&&Date.now()-k<w;)await te(10),xe()===0&&await te(Math.max(0,Math.min(50,w-(Date.now()-k))));l()}finally{G.close()}const C=D&&!D.tainted?D.entries.slice():[];me(a);const K=ge(v),{elementPath:N}=await import("@symbo.ls/element");l();const M=pt(v,N),I=ne(e,t,v,v?.state),E=[],H=c.head||c.querySelector("head");if(H)for(const k of H.querySelectorAll("style")){if(k.sheet&&k.sheet.cssRules)for(const w of k.sheet.cssRules)w.cssText&&E.push(w);if(!E.length){const w=k.textContent||"";w&&E.push({cssText:w})}}const pe=E.map(k=>k.cssText),{globalCSS:R,classRules:x}=Ft(E);let T=qe(a.innerHTML);const U=v?.state?.root,W=se(U,h),ee=At(g,U);ee&&(T=Ae(T,$e(ee,W,h)));const Ie=i&&!i.tainted?{failedRequests:i.failures.slice(),rawLabelKeys:Dt(i.labelKeys,T,W,h,U)}:null;return{html:T,metadata:I,registry:K,brRegistry:M,element:v,emotionCSS:pe,globalCSS:R,classRules:x,fetchSeed:C,document:c,window:f,ssrTranslations:ee||void 0,activeLang:W,diagnostics:Ie}}finally{for(const m of p)m()}},Q=(e,t)=>{const o=Object.getOwnPropertyDescriptor(globalThis,e);return o&&!o.configurable?!o.writable&&!o.set?()=>{}:(globalThis[e]=t,()=>{}):(Object.defineProperty(globalThis,e,{value:t,writable:!0,configurable:!0,enumerable:!0}),()=>{o?Object.defineProperty(globalThis,e,o):delete globalThis[e]})},le=/\{\{\s*([^|{}]+?)\s*\|\s*polyglot\s*\}\}/g,fe=e=>e&&typeof e.parse=="function"?e.parse():e,At=(e,t)=>{const o=fe(t?.translations),n={};for(const s of[e,o])if(!(!s||typeof s!="object"))for(const r in s){const i=fe(s[r]);i&&typeof i=="object"&&(n[r]={...n[r]||{},...i})}return Object.keys(n).length?n:null},$e=(e,t,o)=>e[t]||e[z(void 0,o)]||Object.values(e)[0]||{},Ae=(e,t)=>e.replace(le,(o,n)=>X(t,n.trim())??o),qt=/<script\b[^>]*\btype\s*=\s*["']?application\/json["']?[^>]*>[\s\S]*?<\/script\s*>/gi,Ct=(e,t)=>new RegExp(`(?:^|[^\\w.-])${Qe(t)}(?![\\w-]|\\.\\w)`).test(e),Dt=(e,t,o,n,s)=>{const r=new Set,i=t.replace(qt,"");for(const d of e)/\s/.test(d)||he(d,o,n,s)===void 0&&Ct(i,d)&&r.add(d);for(const d of i.matchAll(le))r.add(d[1].trim());return[...r]},ko=async(e,t={})=>{const{context:o={}}=t,{window:n,document:s}=ue(),r=s.body,{create:i}=await import("@symbo.ls/element"),d=await import("@symbo.ls/utils"),l={...ft,...o.components||{}},g={...d,...o.utils||{},...o.functions||{}};de();let f;try{f=i(e,{node:r},"root",{context:{document:s,window:n,...o,components:l,utils:g}})}catch{}me(r);const c=f?ge(f):{};return{html:qe(r.innerHTML),registry:c,element:f}},qe=e=>e.replace(/(<svg\b[^>]*>)([\s\S]*?)(<\/svg>)/gi,(t,o,n,s)=>{if(n.includes("&lt;")){const r=n.replace(/&lt;/g,"<").replace(/&gt;/g,">").replace(/&amp;/g,"&").replace(/&quot;/g,'"').replace(/&#39;/g,"'");return o+r+s}return t}),Mt=["globalTheme","themeStorageKey","themeRoot","useReset","useVariable","useFontImport","useIconSprite","useSvgSprite","useDocumentTheme","useDefaultConfig","useDefaultIcons","useThemeSuffixedVars","verbose","semanticIcons"],It=/\.[A-Za-z_\\-]/,Ot=e=>{if(typeof e!="string"||!e)return!1;const t=e.replace(/"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'/g,'""').replace(/\[[^\]]*\]/g,"[]");return!It.test(t)},Pt=/^\s*(:root|\[data-theme|\[data-skin)/,Ce=e=>{const t=e&&e.cssText;if(typeof t!="string")return null;if(/^@(-webkit-)?keyframes\b/.test(t))return"keyframes";if(t.startsWith("@font-face"))return null;if(typeof e.selectorText=="string")return Ot(e.selectorText)?Pt.test(e.selectorText)?"root":"reset":null;const o=e.cssRules&&Array.from(e.cssRules);if(o&&o.length&&/^@(media|supports|container)\b/.test(t)){const n=new Set(o.map(Ce));return n.has(null)||n.size!==1?n.has(null)?null:"reset":[...n][0]}return null},Ft=e=>{const t={root:[],reset:[],keyframes:[]},o=[];for(const n of e||[]){const s=Ce(n);s?t[s].push(n.cssText):n&&n.cssText&&o.push(n.cssText)}return{globalCSS:{rootRule:t.root.join(`
3
3
  `),resetRules:t.reset.join(`
4
4
  `),keyframeRules:t.keyframes.join(`
5
- `),fontFaceCSS:""},classRules:o}};let te=new Set;const so=()=>{te=new Set},no=()=>Array.from(te).join(`
6
- `),ro=(e,t)=>e.replace(/<style data-emotion="smbls">[\s\S]*?<\/style>/,t?`<style data-emotion="smbls">
5
+ `),fontFaceCSS:""},classRules:o}};let Z=new Set;const Ro=()=>{Z=new Set},wo=()=>Array.from(Z).join(`
6
+ `),xo=(e,t)=>e.replace(/<style data-emotion="smbls">[\s\S]*?<\/style>/,t?`<style data-emotion="smbls">
7
7
  ${t}
8
- </style>`:""),co=async(e,t={})=>{const{route:o="/",pathname:s,prefetch:r=!0}=t,n=await _e(e,{route:o,pathname:s,prefetch:r});if(!n)return null;const l=e.designSystem||{},u=n.globalCSS;let a=null;try{const f=xt(n.element);Object.keys(f).length&&(a=f)}catch{}const y=n.activeLang;return{html:n.html,css:n.classRules.join(`
9
- `),classRules:n.classRules.slice(),globalCSS:u,resetCss:u.resetRules,fontLinks:Ae(l),metadata:n.metadata||se(e,o),brKeyCount:n.registry?Object.keys(n.registry).length:0,brRegistry:n.brRegistry||{},fetchSeed:n.fetchSeed||[],ssrTranslations:n.ssrTranslations,prefetchedState:a,activeLang:y,diagnostics:n.diagnostics}},xt=e=>{const t={};return ke(e,o=>{const s=o.fetch;if(!(!s||typeof s!="object"||!o.state))for(const r of Array.isArray(s)?s:[s]){const n=r&&typeof r=="object"&&typeof r.as=="string"?r.as:null;if(!n)continue;const l=n.startsWith("~/"),u=l?n.slice(2):n,a=l?o.state.root:o.state,y=fe(a&&a[u]);u&&y!=null&&typeof y!="function"&&(t[u]=y)}}),t},io=async(e,t="/",o={})=>{const{lang:s,themeColor:r,isr:n,hydrate:l=!0,prefetch:u=!0,shell:a,adoptDom:y=!1,pathname:f,eagerImages:i=qe,preloadScripts:c=!0,nonce:d}=o,p=Tt(d),h=s||e.state?.lang||e.app?.metadata?.lang||"en",g=await _e(e,{route:t,pathname:f,prefetch:u});if(!g)return null;const q={...g.metadata};if(r&&(q["theme-color"]=r),typeof a=="string"&&!e.name){const k="\0brender-undeclared-title\0";if(se({...e,name:k},t,g.element,g.element?.state).title===k)for(const E of["title","og:title","twitter:title"])q[E]===g.metadata.title&&delete q[E]}const T=Be(q);for(const k of g.classRules)k&&te.add(k);const B=Array.from(te).join(`
10
- `),I=g.globalCSS,O=`<style data-brender>
11
- ${I.rootRule||""}
12
- ${I.resetRules||""}
13
- ${I.keyframeRules||""}
14
- </style>`,j=B?`<style data-emotion="smbls">
15
- ${B}
16
- </style>`:"",z=Ae(e.designSystem||{}),Y=Object.keys(g.registry).length,A=Et({brRegistry:g.brRegistry,fetchSeed:g.fetchSeed,adoptDom:y});let K="";if(n&&n.clientScript){const k=Number.isInteger(o.depth)?o.depth:t==="/"?0:t.replace(/^\/|\/$/g,"").split("/").length,w=k>0?"../".repeat(k):"./";if(l){let E="";if(g.ssrTranslations){const H=e.polyglot||e.config?.polyglot,S=H?.storagePrefix||"",R=H?.storageLangKey||"",L=[];for(const M in g.ssrTranslations){const J=g.ssrTranslations[M];J&&typeof J=="object"&&L.push(`localStorage.setItem(${JSON.stringify(S+M)},${JSON.stringify(JSON.stringify(J))})`)}if(R){const M=U(void 0,H);L.push(`if(!localStorage.getItem(${JSON.stringify(R)}))localStorage.setItem(${JSON.stringify(R)},${JSON.stringify(M)})`)}L.length&&(E=`<script${p}>try{${L.join(";")}}catch(e){}<\/script>
17
- `)}const G=A.blocks?A.blocks+`
18
- `:"";K=`${E}${G}<script type="module" src="${w}${n.clientScript}"${p}><\/script>`}else K=`<script type="module"${p}>
8
+ </style>`:""),jo=async(e,t={})=>{const{route:o="/",pathname:n,prefetch:s=!0,timeout:r}=t,i=await Le(e,{route:o,pathname:n,prefetch:s,timeout:r});if(!i)return null;const d=e.designSystem||{},l=i.globalCSS;let g=null;try{const c=Bt(i.element);Object.keys(c).length&&(g=c)}catch{}const f=i.activeLang;return{html:i.html,css:i.classRules.join(`
9
+ `),classRules:i.classRules.slice(),globalCSS:l,resetCss:l.resetRules,fontLinks:Me(d),metadata:i.metadata||ne(e,o),brKeyCount:i.registry?Object.keys(i.registry).length:0,brRegistry:i.brRegistry||{},fetchSeed:i.fetchSeed||[],ssrTranslations:i.ssrTranslations,prefetchedState:g,activeLang:f,diagnostics:i.diagnostics}},Bt=e=>{const t={};return Re(e,o=>{const n=o.fetch;if(!(!n||typeof n!="object"||!o.state))for(const s of Array.isArray(n)?n:[n]){const r=s&&typeof s=="object"&&typeof s.as=="string"?s.as:null;if(!r)continue;const i=r.startsWith("~/"),d=i?r.slice(2):r,l=i?o.state.root:o.state,g=fe(l&&l[d]);d&&g!=null&&typeof g!="function"&&(t[d]=g)}}),t},_o=async(e,t="/",o={})=>{const{lang:n,themeColor:s,isr:r,hydrate:i=!0,prefetch:d=!0,shell:l,adoptDom:g=!1,pathname:f,eagerImages:c=De,preloadScripts:a=!0,nonce:p,timeout:m}=o,y=Ut(p),S=n||e.state?.lang||e.app?.metadata?.lang||"en",h=await Le(e,{route:t,pathname:f,prefetch:d,timeout:m});if(!h)return null;const q={...h.metadata};if(s&&(q["theme-color"]=s),typeof l=="string"&&!e.name){const R="\0brender-undeclared-title\0";if(ne({...e,name:R},t,h.element,h.element?.state).title===R)for(const T of["title","og:title","twitter:title"])q[T]===h.metadata.title&&delete q[T]}const _=We(q);for(const R of h.classRules)R&&Z.add(R);const F=Array.from(Z).join(`
10
+ `),$=h.globalCSS,B=`<style data-brender>
11
+ ${$.rootRule||""}
12
+ ${$.resetRules||""}
13
+ ${$.keyframeRules||""}
14
+ </style>`,G=F?`<style data-emotion="smbls">
15
+ ${F}
16
+ </style>`:"",D=Me(e.designSystem||{}),v=Object.keys(h.registry).length,C=Ht({brRegistry:h.brRegistry,fetchSeed:h.fetchSeed,adoptDom:g});let K="";if(r&&r.clientScript){const R=Number.isInteger(o.depth)?o.depth:t==="/"?0:t.replace(/^\/|\/$/g,"").split("/").length,x=R>0?"../".repeat(R):"./";if(i){const T=ot({polyglot:e.polyglot||e.config?.polyglot,translations:h.ssrTranslations,lang:h.activeLang,pathname:f||t,attrs:y}),U=T?T+`
17
+ `:"",W=C.blocks?C.blocks+`
18
+ `:"";K=`${U}${W}<script type="module" src="${x}${r.clientScript}"${y}><\/script>`}else K=`<script type="module"${y}>
19
19
  {
20
20
  const brEls = document.querySelectorAll('body > :not(script):not(style)')
21
21
  const observer = new MutationObserver((mutations) => {
@@ -32,22 +32,22 @@ ${B}
32
32
  observer.observe(document.body, { childList: true })
33
33
  }
34
34
  <\/script>
35
- <script type="module" src="${w}${n.clientScript}"${p}><\/script>`}const N={...e.config||{}};e.polyglot&&!N.polyglot&&(N.polyglot=e.polyglot);const v=N.polyglot;let C=T;if(v){const k={...v.translations||{}};for(const w in g.ssrTranslations||{})k[w]={...k[w]||{},...g.ssrTranslations[w]};C=Te(T,Ee(k,g.activeLang,v))}const D=g.diagnostics;if(D)for(const k of C.matchAll(ae)){const w=k[1].trim();D.rawLabelKeys.includes(w)||D.rawLabelKeys.push(w)}const V=_t(g.html,i);return typeof a=="string"?{html:Ke(a,{headTags:`${C}
36
- ${A.meta}`,headEnd:[z,O,j].filter(Boolean).join(`
37
- `),body:V,depth:Number.isInteger(o.depth)?o.depth:0,bootScripts:A.blocks,lang:s||(v?g.activeLang:void 0),preloadScripts:c}),route:t,brKeyCount:Y,shell:!0,diagnostics:D}:{html:`<!DOCTYPE html>
38
- <html lang="${h}">
35
+ <script type="module" src="${x}${r.clientScript}"${y}><\/script>`}const N={...e.config||{}};e.polyglot&&!N.polyglot&&(N.polyglot=e.polyglot);const M=N.polyglot;let I=_;if(M){const R={...M.translations||{}};for(const x in h.ssrTranslations||{})R[x]={...R[x]||{},...h.ssrTranslations[x]};I=Ae(_,$e(R,h.activeLang,M))}const E=h.diagnostics;if(E)for(const R of I.matchAll(le)){const x=R[1].trim();E.rawLabelKeys.includes(x)||E.rawLabelKeys.push(x)}const H=Nt(h.html,c);return typeof l=="string"?{html:Je(l,{headTags:`${I}
36
+ ${C.meta}`,headEnd:[D,B,G].filter(Boolean).join(`
37
+ `),body:H,depth:Number.isInteger(o.depth)?o.depth:0,bootScripts:C.blocks,lang:n||(M?h.activeLang:void 0),preloadScripts:a}),route:t,brKeyCount:v,shell:!0,diagnostics:E}:{html:`<!DOCTYPE html>
38
+ <html lang="${S}">
39
39
  <head>
40
- ${C}${n&&n.clientScript&&l?`
41
- ${A.meta}`:""}
42
- ${z}
40
+ ${I}${r&&r.clientScript&&i?`
41
+ ${C.meta}`:""}
42
+ ${D}
43
43
 
44
- ${O}
45
- ${j}
44
+ ${B}
45
+ ${G}
46
46
  </head>
47
47
  <body>
48
- ${V}
48
+ ${H}
49
49
  ${K}
50
50
  </body>
51
- </html>`,route:t,brKeyCount:Y,diagnostics:D}},qe=2,jt=/<img\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,vt=/\sloading\s*=/i,_t=(e,t=qe)=>{if(t===!1||t==null||typeof e!="string")return e;const o=Math.max(0,Number(t)||0);let s=0;return e.replace(jt,r=>s++<o||vt.test(r)?r:r.replace(/^<img\b/i,'<img loading="lazy"'))},Ae=e=>{if(!e)return"";const t=e.font_family||e.fontFamily||{},o=new Set;for(const r of Object.values(t)){if(typeof r!="string")continue;const n=r.match(/'([^']+)'/);n&&o.add(n[1])}return o.size?['<link rel="preconnect" href="https://fonts.googleapis.com">','<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',`<link href="https://fonts.googleapis.com/css2?${[...o].map(r=>`family=${r.replace(/\s+/g,"+")}:wght@300;400;500;600;700`).join("&")}&display=swap" rel="stylesheet">`].join(`
52
- `):""},Et=({brRegistry:e,fetchSeed:t,adoptDom:o=!1}={})=>{const s=o&&e&&Object.keys(e).length?Je(e):"",r=Ye(t);return{meta:`<meta name="brender" content="${s?"hydrate":"replace"}">`,blocks:[s,r].filter(Boolean).join(`
53
- `)}},Tt=e=>{if(e==null||e==="")return"";const t=String(e);if(!/^[A-Za-z0-9+/=_-]+$/.test(t))throw new Error("brender: a nonce is base64 or base64url text");return` nonce="${t}"`};export{qe as EAGER_IMAGES_DEFAULT,Se as SMBLS_SSR_SPECIFIER,to as adoptionScripts,no as getAccumulatedEmotionCSS,_t as markLazyImages,Et as prerenderParts,_e as render,oo as renderElement,io as renderPage,co as renderRoute,ro as replaceEmotionCSS,so as resetGlobalCSSCache,wt as splitCapturedCSS};
51
+ </html>`,route:t,brKeyCount:v,diagnostics:E}},De=2,Gt=/<img\b(?:[^>"']|"[^"]*"|'[^']*')*>/gi,Kt=/\sloading\s*=/i,Nt=(e,t=De)=>{if(t===!1||t==null||typeof e!="string")return e;const o=Math.max(0,Number(t)||0);let n=0;return e.replace(Gt,s=>n++<o||Kt.test(s)?s:s.replace(/^<img\b/i,'<img loading="lazy"'))},Me=e=>{if(!e)return"";const t=e.font_family||e.fontFamily||{},o=new Set;for(const s of Object.values(t)){if(typeof s!="string")continue;const r=s.match(/'([^']+)'/);r&&o.add(r[1])}return o.size?['<link rel="preconnect" href="https://fonts.googleapis.com">','<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',`<link href="https://fonts.googleapis.com/css2?${[...o].map(s=>`family=${s.replace(/\s+/g,"+")}:wght@300;400;500;600;700`).join("&")}&display=swap" rel="stylesheet">`].join(`
52
+ `):""},Ht=({brRegistry:e,fetchSeed:t,adoptDom:o=!1}={})=>{const n=o&&e&&Object.keys(e).length?Ve(e):"",s=st(t);return{meta:`<meta name="brender" content="${n?"hydrate":"replace"}">`,blocks:[n,s].filter(Boolean).join(`
53
+ `)}},Ut=e=>{if(e==null||e==="")return"";const t=String(e);if(!/^[A-Za-z0-9+/=_-]+$/.test(t))throw new Error("brender: a nonce is base64 or base64url text");return` nonce="${t}"`};export{De as EAGER_IMAGES_DEFAULT,Se as SMBLS_SSR_SPECIFIER,So as adoptionScripts,wo as getAccumulatedEmotionCSS,Nt as markLazyImages,Ht as prerenderParts,Le as render,ko as renderElement,_o as renderPage,jo as renderRoute,xo as replaceEmotionCSS,Ro as resetGlobalCSSCache,Ft as splitCapturedCSS};
package/env.js CHANGED
@@ -108,8 +108,9 @@ export const createEnv = (html = '<!DOCTYPE html><html><head></head><body></body
108
108
  // them): `localStorage` / `sessionStorage` are the newest environment's
109
109
  // stores, and `history` is the first environment's stub when the host has
110
110
  // none. render() swaps the bare storage and location to the rendering
111
- // environment's own for the length of a render; overlapping renders still
112
- // share them (a render queue or per-render globals — an open decision).
111
+ // environment's own for the length of a render, and runs one render at a
112
+ // time per process, so no two renders hold them at once (render.js "One
113
+ // render at a time").
113
114
  globalThis.localStorage = own.localStorage
114
115
  globalThis.sessionStorage = own.sessionStorage
115
116
  if (!globalThis.history) globalThis.history = own.history
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/brender",
3
- "version": "3.14.666",
3
+ "version": "3.14.667",
4
4
  "license": "CC-BY-NC-4.0",
5
5
  "type": "module",
6
6
  "module": "./dist/esm/index.js",
@@ -52,7 +52,7 @@
52
52
  "@symbo.ls/fetch": "^3.14.666",
53
53
  "@symbo.ls/helmet": "^3.14.670",
54
54
  "@symbo.ls/scratch": "^3.14.789",
55
- "@symbo.ls/utils": "^3.14.787",
55
+ "@symbo.ls/utils": "^3.14.788",
56
56
  "esbuild": "^0.28.1",
57
57
  "linkedom": "^0.16.8"
58
58
  },
package/render.js CHANGED
@@ -13,7 +13,7 @@ import { composeIntoShell } from './shell.js'
13
13
  import { fetchSSRTranslations } from './prefetch.js'
14
14
  import { createEarlySeedScript } from '@symbo.ls/fetch'
15
15
  import { headerEntries } from '@symbo.ls/fetch/rest'
16
- import { brenderRegistryBlock, escapeRegExp, jsonForScript, polyglotFallbackLang } from '@symbo.ls/utils'
16
+ import { brenderRegistryBlock, escapeRegExp, jsonForScript, polyglotFallbackLang, polyglotLangStorageKey, polyglotAddressLang, polyglotStartupScript } from '@symbo.ls/utils'
17
17
 
18
18
  // funcql plugin — enables evaluation of funcql schemas as property values
19
19
  // during SSR. Async-imported to avoid polluting non-funcql codepaths.
@@ -111,6 +111,23 @@ const ssrGetActiveLang = function () {
111
111
  return activeLangOf(this?.state?.root, this?.context?.polyglot)
112
112
  }
113
113
 
114
+ // ── The address's language ──────────────────────────────────────────────────
115
+ // A page at an address whose first segment is one of the project's languages
116
+ // (`/en/…` with `polyglot.languages: ['ka', 'en']`) is that language's page
117
+ // (@symbo.ls/utils polyglotAddressLang). In the browser the app starts in it:
118
+ // the page's pre-boot script stores the address's language under polyglot's
119
+ // storage key (`storageLangKey`, utils polyglotLangStorageKey) before the app
120
+ // starts, and the app starts in the stored language (polyglot initPolyglot;
121
+ // smbls prepareState, for static labels). A render's environment starts with
122
+ // empty storage, so a server render started in the default language and
123
+ // switched only when a router guard read the address — after the shell had
124
+ // made its first requests: on every /en page of the xma.info test bed the
125
+ // header's menu was asked for, and recorded in the page's fetch seed, in
126
+ // `ka`; the browser asked in `en`, found no answer for that in the seed, and
127
+ // asked the network again on every cold /en load (2026-10-17, 13 of 13
128
+ // pages). render() now stores the address's language in its own
129
+ // environment's storage before the app starts, as the browser has it.
130
+
114
131
  // Deep clone that preserves functions and avoids circular refs
115
132
  const structuredCloneDeep = (obj, seen = new WeakMap()) => {
116
133
  if (obj === null || typeof obj !== 'object') return obj
@@ -718,21 +735,6 @@ const walkElements = (root, visit) => {
718
735
  walk(root)
719
736
  }
720
737
 
721
- /**
722
- * Renders a Symbols/DOMQL project to HTML on the server.
723
- *
724
- * Accepts project data as a plain object (matching what ProjectDataService provides)
725
- * or as a pre-loaded smbls context. Runs DOMQL in a linkedom virtual DOM,
726
- * assigns data-br keys for hydration, and extracts page metadata for SEO.
727
- *
728
- * @param {object} data - Project data object with: pages, components, designSystem,
729
- * state, functions, methods, snippets, files, app, config/settings
730
- * @param {object} [options]
731
- * @param {string} [options.route='/'] - The route/page to render
732
- * @param {object} [options.state] - State overrides
733
- * @param {object} [options.context] - Additional context overrides
734
- * @returns {Promise<{ html: string, metadata: object, registry: object, element: object }>}
735
- */
736
738
  // The payload keys render() puts on the context itself (below `ctx`): the
737
739
  // generic copy of every other top-level key skips them — cloning one only for
738
740
  // the explicit key to replace it was a deep clone of `functions`, `methods`
@@ -761,7 +763,9 @@ const inFlightRequests = () => _inFlight
761
763
  // can carry them (@symbo.ls/fetch createEarlySeedScript) and the hydrating
762
764
  // client adopts them instead of asking again. A second render overlapping
763
765
  // in the same isolate taints the recording: another project's answers must
764
- // never land in this page, so an overlapped render carries none.
766
+ // never land in this page, so an overlapped render carries none. Renders run
767
+ // one at a time (see "One render at a time"), so the only render that can
768
+ // overlap another is one abandoned at its timeout and still unwinding.
765
769
  // Only a GET whose headers are Accept-Language / Accept at most is recorded
766
770
  // (never a signed-in or keyed call), only an ok JSON or text answer, at most
767
771
  // SEED_ENTRY_MAX bytes each and SEED_TOTAL_MAX per page.
@@ -919,7 +923,147 @@ export const adoptionScripts = ({ brRegistry, fetchSeed, hydrateFlag = true } =
919
923
  (fetchSeed && fetchSeed.length ? '\n' + createEarlySeedScript(fetchSeed) : '')
920
924
  }
921
925
 
922
- export const render = async (data, options = {}) => {
926
+ // ── One render at a time ────────────────────────────────────────────────────
927
+ // A render puts its environment on the process's BARE globals — `document`,
928
+ // `location`, `localStorage`, `sessionStorage` (swapGlobal below) and
929
+ // `window` (createEnv) — because the framework reads them bare, and it shares
930
+ // process-wide records with the code it runs (the fetch seed and diagnostics
931
+ // above, the data-br key counter, the CSS engine's reset). Every render owns
932
+ // its location, Web Storage, history (createEnv) and query store
933
+ // (`isolateFetchCache`), yet renders that overlapped in one process still
934
+ // swapped those bare globals over each other: of five overlapping renders,
935
+ // the page code of three read another render's address from `location`.
936
+ // So render() runs ONE AT A TIME per process (Nika, 2026-10-17: "Render
937
+ // queue"): a render starts once the render called before it has ended —
938
+ // returned, thrown or timed out. `smbls build` and `smbls brender` already
939
+ // rendered one route after another; for them nothing changes.
940
+ //
941
+ // A render that never ends would hold every later one, so each render has a
942
+ // time limit — `options.timeout` ms, RENDER_TIMEOUT by default, counted from
943
+ // when its turn starts (the wait for the renders before it does not count,
944
+ // and neither does building the process's framework bundle). When it is up
945
+ // the render is ABANDONED: the call rejects with a BRENDER_RENDER_TIMEOUT
946
+ // error naming the address and route, the bare globals it put in place go
947
+ // back to what it found, and the next render starts. Its own steps after the
948
+ // wait it hung on find the render closed and stop there (`live` in
949
+ // renderWithDiagnostics): it never puts its globals back over a later
950
+ // render's, never starts its flush or key pass, and its fetch seed and
951
+ // diagnostics stay open until it has unwound, so a render that runs meanwhile
952
+ // carries none — the abandoned render's late requests would land in them.
953
+ // What it cannot stop: framework or page code of the abandoned render that
954
+ // resumes with its wait (a slow shared-library or dependency load, a page's
955
+ // own await) runs during a later render, and what that code reads from the
956
+ // BARE globals, or writes through them, is the later render's — so is
957
+ // process-wide framework state such as the active root state that
958
+ // `getRootState()` falls back to. Code that goes through its own element or
959
+ // context (`el.context.window`, the router, the fetch plugin's store) stays
960
+ // in its own environment.
961
+ const RENDER_TIMEOUT = 30000
962
+ // setTimeout's ceiling: a larger delay (Infinity included) fires at once.
963
+ const TIMER_MAX = 2147483647
964
+
965
+ // Settles when the render called last has ended, however it ended.
966
+ let _renderQueue = Promise.resolve()
967
+ const noop = () => {}
968
+
969
+ const inRenderQueue = (run) => {
970
+ const turn = _renderQueue.then(run)
971
+ _renderQueue = turn.then(noop, noop)
972
+ return turn
973
+ }
974
+
975
+ // `options.timeout`: milliseconds; 0, false or Infinity for no limit.
976
+ const renderTimeoutOf = (value) => {
977
+ if (value === undefined) return RENDER_TIMEOUT
978
+ if (value === 0 || value === false || value === Infinity) return 0
979
+ if (typeof value !== 'number' || !(value > 0)) {
980
+ throw new TypeError(`brender: options.timeout is a number of milliseconds, or 0 / false for no limit — not ${JSON.stringify(value)}`)
981
+ }
982
+ return Math.min(value, TIMER_MAX)
983
+ }
984
+
985
+ const renderTimeoutError = (options, timeout) => {
986
+ const route = options.route || '/'
987
+ const pathname = options.pathname || route
988
+ const where = pathname === route ? route : `${pathname} (route ${route})`
989
+ const err = new Error(`brender: the render of ${where} did not finish within ${timeout} ms and was abandoned, so the renders after it can run (options.timeout)`)
990
+ err.code = 'BRENDER_RENDER_TIMEOUT'
991
+ err.route = route
992
+ err.pathname = pathname
993
+ err.timeout = timeout
994
+ return err
995
+ }
996
+
997
+ // The end of an abandoned render's turn: the bare globals it swapped go back
998
+ // now, and its own later steps find it closed.
999
+ const abandon = (turn) => {
1000
+ turn.closed = true
1001
+ for (const undo of turn.undo.splice(0)) undo()
1002
+ }
1003
+
1004
+ const once = (fn) => {
1005
+ let done = false
1006
+ return () => {
1007
+ if (done) return
1008
+ done = true
1009
+ fn()
1010
+ }
1011
+ }
1012
+
1013
+ /**
1014
+ * Renders a Symbols/DOMQL project to HTML on the server.
1015
+ *
1016
+ * Accepts project data as a plain object (matching what ProjectDataService provides)
1017
+ * or as a pre-loaded smbls context. Runs DOMQL in a linkedom virtual DOM,
1018
+ * assigns data-br keys for hydration, and extracts page metadata for SEO.
1019
+ * One render at a time per process: a call waits until the render called
1020
+ * before it has ended (see "One render at a time").
1021
+ *
1022
+ * @param {object} data - Project data object with: pages, components, designSystem,
1023
+ * state, functions, methods, snippets, files, app, config/settings
1024
+ * @param {object} [options]
1025
+ * @param {string} [options.route='/'] - The route/page to render
1026
+ * @param {string} [options.pathname] - The address the page is rendered at (default: the route)
1027
+ * @param {object} [options.state] - State overrides
1028
+ * @param {object} [options.context] - Additional context overrides
1029
+ * @param {boolean} [options.prefetch=false] - Fetch table/RPC labels first and wait for the page's requests
1030
+ * @param {number|false} [options.timeout=30000] - Milliseconds the render may run once its turn
1031
+ * starts; then it is abandoned and rejects with a `BRENDER_RENDER_TIMEOUT` error. 0 or false: no limit
1032
+ * @returns {Promise<{ html: string, metadata: object, registry: object, element: object }>}
1033
+ */
1034
+ export const render = (data, options = {}) => {
1035
+ let timeout
1036
+ try {
1037
+ timeout = renderTimeoutOf(options.timeout)
1038
+ } catch (err) {
1039
+ return Promise.reject(err)
1040
+ }
1041
+ return inRenderQueue(() => renderInTurn(data, options, timeout))
1042
+ }
1043
+
1044
+ const renderInTurn = async (data, options, timeout) => {
1045
+ // The framework bundle is the process's, not this route's: building it
1046
+ // (esbuild over smbls's source on a process's first render) is not timed.
1047
+ await bundleCreateDomql()
1048
+ const turn = { closed: false, undo: [] }
1049
+ const work = renderOnce(data, options, turn)
1050
+ if (!timeout) return work
1051
+ let timer
1052
+ const limit = new Promise((resolve, reject) => {
1053
+ timer = setTimeout(() => {
1054
+ abandon(turn)
1055
+ reject(renderTimeoutError(options, timeout))
1056
+ }, timeout)
1057
+ })
1058
+ try {
1059
+ // An abandoned render's own rejection, later, lands here unobserved.
1060
+ return await Promise.race([work, limit])
1061
+ } finally {
1062
+ clearTimeout(timer)
1063
+ }
1064
+ }
1065
+
1066
+ const renderOnce = async (data, options, turn) => {
923
1067
  const { route = '/', pathname, state: stateOverrides, context: contextOverrides, prefetch = false } = options
924
1068
  // pathname is the actual URL path (e.g. /podcast/abc-123), route is the page pattern (e.g. /podcast/:id)
925
1069
  const locationPath = pathname || route
@@ -928,13 +1072,19 @@ export const render = async (data, options = {}) => {
928
1072
  trackFetches()
929
1073
  const diag = openRenderRecord(DIAG, () => ({ failures: [], labelKeys: new Set(), tainted: false }))
930
1074
  try {
931
- return await renderWithDiagnostics(data, { route, locationPath, stateOverrides, contextOverrides, prefetch }, diag.record)
1075
+ return await renderWithDiagnostics(data, { route, locationPath, stateOverrides, contextOverrides, prefetch }, diag.record, turn)
932
1076
  } finally {
933
1077
  diag.close()
934
1078
  }
935
1079
  }
936
1080
 
937
- const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides, contextOverrides, prefetch }, diag) => {
1081
+ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides, contextOverrides, prefetch }, diag, turn) => {
1082
+ // After every wait: a render abandoned at its timeout stops here (see
1083
+ // "One render at a time").
1084
+ const live = () => {
1085
+ if (turn.closed) throw new Error('brender: this render was abandoned at its timeout')
1086
+ }
1087
+
938
1088
  // The page's data is NOT fetched here. The fetch plugin runs during the
939
1089
  // render below with the real element — its `transform(data, el, state)`,
940
1090
  // its `children` re-run on the answer — and the post-create flush waits
@@ -958,8 +1108,9 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
958
1108
  try {
959
1109
  ssrTranslations = await fetchSSRTranslations(data)
960
1110
  } catch (e) {
961
- console.warn('[brender] SSR translation fetch failed:', e.message)
1111
+ if (!turn.closed) console.warn('[brender] SSR translation fetch failed:', e.message)
962
1112
  }
1113
+ live()
963
1114
  }
964
1115
 
965
1116
  const { window, document } = createEnv()
@@ -975,19 +1126,23 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
975
1126
  // loaded there, and one shared store handed them to the next page of the
976
1127
  // build (createEnv). Swapped by descriptor and put back however the render
977
1128
  // ends, a throw included — a host's own Web Storage global (Node's is a
978
- // lazy accessor) is never read or lost.
1129
+ // lazy accessor) is never read or lost. Each goes back once: at the end of
1130
+ // the render, or at its timeout (`turn.undo`) — then never again, when the
1131
+ // abandoned render unwinds over a later render's globals.
979
1132
  const restoreGlobals = [
980
1133
  swapGlobal('document', document),
981
1134
  swapGlobal('location', window.location),
982
1135
  swapGlobal('localStorage', window.localStorage),
983
1136
  swapGlobal('sessionStorage', window.sessionStorage)
984
- ]
1137
+ ].map(once)
1138
+ turn.undo.push(...restoreGlobals)
985
1139
  try {
986
1140
 
987
1141
  // Import createDomqlElement via bundled smbls source.
988
1142
  // The smbls monorepo uses extensionless/directory imports that Node.js ESM
989
1143
  // can't resolve natively, so we bundle it with esbuild first.
990
1144
  const { createDomqlElement } = await bundleCreateDomql()
1145
+ live()
991
1146
 
992
1147
  const app = structuredCloneDeep(data.app || {})
993
1148
 
@@ -1003,6 +1158,12 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
1003
1158
 
1004
1159
  // Inject SSR translations into polyglot config and root state
1005
1160
  const polyglotConfig = config.polyglot ? { ...config.polyglot } : undefined
1161
+
1162
+ // The address's language in this render's storage before the app starts,
1163
+ // as the browser has it (see "The address's language").
1164
+ const addressLang = polyglotAddressLang(locationPath, polyglotConfig)
1165
+ if (addressLang) window.localStorage.setItem(polyglotLangStorageKey(polyglotConfig), addressLang)
1166
+
1006
1167
  if (ssrTranslations && polyglotConfig) {
1007
1168
  polyglotConfig.translations = {
1008
1169
  ...(polyglotConfig.translations || {}),
@@ -1107,6 +1268,7 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
1107
1268
 
1108
1269
  // Add funcql plugin if available (enables funcql schema evaluation in exec())
1109
1270
  const funcql = await getFuncqlPlugin()
1271
+ live()
1110
1272
  if (funcql) {
1111
1273
  ctx.plugins = ctx.plugins || []
1112
1274
  if (!ctx.plugins.some(p => p.name === 'funcql')) {
@@ -1123,6 +1285,7 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
1123
1285
  let element
1124
1286
  try {
1125
1287
  element = await createDomqlElement(app, ctx)
1288
+ live()
1126
1289
 
1127
1290
  // Allow async operations (fetch callbacks, state updates, re-renders) to flush.
1128
1291
  // DOMQL's fetch plugin fires on element creation and updates state
@@ -1141,11 +1304,12 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
1141
1304
  const flushCap = prefetch ? 2000 : 50
1142
1305
  const sleep = (ms) => new Promise(r => setTimeout(r, ms))
1143
1306
  await sleep(Math.min(50, flushCap))
1144
- while (prefetch && inFlightRequests() > 0 && Date.now() - flushStart < flushCap) {
1307
+ while (prefetch && !turn.closed && inFlightRequests() > 0 && Date.now() - flushStart < flushCap) {
1145
1308
  await sleep(10)
1146
1309
  // a settled batch: another quiet period for what it triggers
1147
1310
  if (inFlightRequests() === 0) await sleep(Math.max(0, Math.min(50, flushCap - (Date.now() - flushStart))))
1148
1311
  }
1312
+ live()
1149
1313
  } finally {
1150
1314
  recording.close()
1151
1315
  }
@@ -1159,6 +1323,7 @@ const renderWithDiagnostics = async (data, { route, locationPath, stateOverrides
1159
1323
  // Build element path → brKey registry for client-side true hydration.
1160
1324
  // Walks the element tree after mapKeysToElements has set __brKey on each element.
1161
1325
  const { elementPath } = await import('@symbo.ls/element')
1326
+ live()
1162
1327
  const brRegistry = buildPathRegistry(element, elementPath)
1163
1328
 
1164
1329
  // Extract metadata for the rendered route
@@ -1490,17 +1655,18 @@ export const replaceEmotionCSS = (html, newCSS) => {
1490
1655
  * @param {object} data - Full project data
1491
1656
  * @param {object} [options]
1492
1657
  * @param {string} [options.route='/'] - Route to render
1658
+ * @param {number|false} [options.timeout] - render()'s time limit (30 s by default)
1493
1659
  * @returns {Promise<{ html: string, css: string, resetCss: string, fontLinks: string, metadata: object, brKeyCount: number }>}
1494
1660
  */
1495
1661
  export const renderRoute = async (data, options = {}) => {
1496
1662
  // `prefetch: false` fetches no table/RPC labels first and does not wait for
1497
1663
  // the page's requests — the publish-time pre-render (smbls publish →
1498
1664
  // ssrCache) renders data-free.
1499
- const { route = '/', pathname, prefetch = true } = options
1665
+ const { route = '/', pathname, prefetch = true, timeout } = options
1500
1666
 
1501
1667
  // Use the full render pipeline which handles polyglot, prefetch, emotion, etc.
1502
1668
  // Pass pathname so the virtual DOM location reflects the actual URL (for dynamic routes)
1503
- const result = await render(data, { route, pathname, prefetch })
1669
+ const result = await render(data, { route, pathname, prefetch, timeout })
1504
1670
  if (!result) return null
1505
1671
 
1506
1672
  const ds = data.designSystem || {}
@@ -1595,13 +1761,15 @@ const collectFetchedState = (element) => {
1595
1761
  * @param {string} [options.shell] - A built app document (e.g. dist/index.html)
1596
1762
  * to render INTO instead of emitting a standalone document (see shell.js).
1597
1763
  * The result then carries `shell: true`.
1764
+ * @param {number|false} [options.timeout] - render()'s time limit (30 s by
1765
+ * default; 0 or false: none) — see render()
1598
1766
  * @param {number} [options.depth] - Directory depth of the output file: rebases
1599
1767
  * the shell's relative asset URLs (with `shell`, default 0) and the ISR
1600
1768
  * client script path (default: derived from the route).
1601
1769
  * @returns {Promise<{ html: string, route: string, brKeyCount: number, shell?: true }>}
1602
1770
  */
1603
1771
  export const renderPage = async (data, route = '/', options = {}) => {
1604
- const { lang, themeColor, isr, hydrate = true, prefetch = true, shell, adoptDom = false, pathname, eagerImages = EAGER_IMAGES_DEFAULT, preloadScripts = true, nonce } = options
1772
+ const { lang, themeColor, isr, hydrate = true, prefetch = true, shell, adoptDom = false, pathname, eagerImages = EAGER_IMAGES_DEFAULT, preloadScripts = true, nonce, timeout } = options
1605
1773
  const nonceAttr = nonceAttribute(nonce)
1606
1774
 
1607
1775
  // Detect lang from project config, app metadata, or default
@@ -1612,7 +1780,7 @@ export const renderPage = async (data, route = '/', options = {}) => {
1612
1780
  // canonical/og:url built from it read it. Without it a route was rendered
1613
1781
  // at its own pattern: the `/*` page wrote a `/*` canonical into 404.html
1614
1782
  // (xma.info report, brender build item 7).
1615
- const result = await render(data, { route, pathname, prefetch })
1783
+ const result = await render(data, { route, pathname, prefetch, timeout })
1616
1784
  if (!result) return null
1617
1785
 
1618
1786
  const metadata = { ...result.metadata }
@@ -1681,26 +1849,21 @@ export const renderPage = async (data, route = '/', options = {}) => {
1681
1849
  // Seed client-side polyglot with SSR translations so that
1682
1850
  // el.call('polyglot', key) resolves immediately during hydration
1683
1851
  // instead of showing raw keys until the async fetch completes.
1684
- let translationSeed = ''
1685
- if (result.ssrTranslations) {
1686
- const polyglotCfg = data.polyglot || data.config?.polyglot
1687
- const storagePrefix = polyglotCfg?.storagePrefix || ''
1688
- const storageLangKey = polyglotCfg?.storageLangKey || ''
1689
- const seedEntries = []
1690
- for (const lang in result.ssrTranslations) {
1691
- const map = result.ssrTranslations[lang]
1692
- if (map && typeof map === 'object') {
1693
- seedEntries.push(`localStorage.setItem(${JSON.stringify(storagePrefix + lang)},${JSON.stringify(JSON.stringify(map))})`)
1694
- }
1695
- }
1696
- if (storageLangKey) {
1697
- const defaultLang = polyglotFallbackLang(undefined, polyglotCfg)
1698
- seedEntries.push(`if(!localStorage.getItem(${JSON.stringify(storageLangKey)}))localStorage.setItem(${JSON.stringify(storageLangKey)},${JSON.stringify(defaultLang)})`)
1699
- }
1700
- if (seedEntries.length) {
1701
- translationSeed = `<script${nonceAttr}>try{${seedEntries.join(';')}}catch(e){}</script>\n`
1702
- }
1703
- }
1852
+ // For a visitor with no language stored yet it stores the page's
1853
+ // language — the address's on a /<lang>/… page (see "The address's
1854
+ // language") — so the client starts where the server started, and its
1855
+ // first requests are the ones the page's fetch seed recorded. It stored
1856
+ // the default language: a fresh client on a /en page booted in the
1857
+ // default and asked for the header's menu again. One helper with the
1858
+ // platform renderer (@symbo.ls/utils polyglotStartupScript).
1859
+ const startup = polyglotStartupScript({
1860
+ polyglot: data.polyglot || data.config?.polyglot,
1861
+ translations: result.ssrTranslations,
1862
+ lang: result.activeLang,
1863
+ pathname: pathname || route,
1864
+ attrs: nonceAttr
1865
+ })
1866
+ const translationSeed = startup ? startup + '\n' : ''
1704
1867
  // The registry and the fetch seed as data blocks; the page's mark is
1705
1868
  // its <meta name="brender"> (no inline flag a policy could block).
1706
1869
  const blocks = prerender.blocks ? prerender.blocks + '\n' : ''