Changelogs
Frontend Changelog
Changelog for the Laioutr frontend product following Keep a Changelog and Semantic Versioning.
All notable changes to the Laioutr frontend (Nuxt based storefront, Frontend Core integration, and built in frontend features) will be documented in this file.
0.41.0 - 2026-08-11
Minor Changes
- Markets can be set to
draft, and a project can name its default market.
A draft market still serves its own host so it can be checked before launch, but it is excluded from hreflang alternates,og:locale:alternate,x-default, and market switchers, and its pages are served withnoindex, nofollow.switchMarketUrlreturns'#market-not-active'for a draft target. Status is read throughRenderMarket.isLinkableandisIndexablerather than thestatusmember, so a future status changes one mapping instead of every consumer.RcProject.defaultMarketIdreplaces the implicit "first market in the configuration" rule forx-default, the primary route path, the unknown-host fallback, and nuxt-i18n'sdefaultLocale. Which market that was depended on the order Cockpit happened to return them in, so a project whose primary market did not sort first was pointingx-defaultat the wrong one. Leaving the new field unset preserves the previous behaviour. The resolved default is reachable both asRenderI18nConfig.defaultMarketand asRenderMarket.isDefault, so a component holding a single market can tell whether it is the default one without reading the whole config.
Breaking:RenderI18nConfig.marketsnow contains only markets that may be linked to. The complete list, including drafts, moved toRenderI18nConfig.allMarkets. Code that renders a market switcher needs no change and starts honouring status automatically; code that needs every configured market (routing, host resolution, preview) must switch toallMarkets.// Before: one list, used for both const switcherMarkets = i18nConfig.markets; const routableMarkets = i18nConfig.markets; // After const switcherMarkets = i18nConfig.markets; // linkable only, now status-aware const routableMarkets = i18nConfig.allMarkets; // every configured market
Patch Changes
- Breaking: Local dev hostnames for projects hosted on
*.app.laioutr.techdrop that suffix instead of folding it into the label. A market onacme-shop.app.laioutr.techis now reachable atacme-shop.local.laioutr.techrather thanacme-shop-app-laioutr-tech.local.laioutr.tech. The same host drives market resolution and the dev cookie domain, so all three stay in step — update any bookmark orallowedHostsentry that named the old form.
The startup banner no longer prints duringnuxi prepareandnuxi typecheck, only when the app actually boots. - Switching market or language in the Studio reloads the previewed content
The preview kept rendering whichever market it was opened with. Switching from German to Dutch — or from German to English inside a single market — left the previous menus, product data and prices on screen, so the preview showed one market's content under another market's settings.
Orchestr results are scoped to a market and a language, but the client caches them under a key that carries neither. Every query after a switch therefore read as a cache hit and no request was ever sent. The cached results are now dropped when the selection changes, and the page refetches under the market and language now selected.
Reselecting the market already being previewed still serves the cache. Storefront rendering is unaffected; the change is limited to the Studio preview.
0.40.3 - 2026-08-07
Patch Changes
linkResolver.resolve()no longer drops a link'squery.
Every Laioutr page route carrieslocalizedPaths, so internal links always resolved through the localized-path branch — which ignoredqueryand returned before the vue-router fallback that applied it. A header search resolving{ type: 'pageType', pageType: ProductSearchPage, query: { q: term } }landed on/searchwithout?q=.reference,pageandpageTypelinks were affected alike.urlandanchorlinks kept theirqueryin the type but dropped it on resolve; they now carry it too. The query is merged into whatever thehrefalready has — the link's ownquerywins on a key collision — and always lands before the fragment. A link without aquerystill passes its href through untouched.
Query strings go through vue-router'sstringifyQuery, so a resolved link matches whatrouter.resolve()would have produced.
0.40.2 - 2026-08-06
Patch Changes
- Session cookies now survive the Cockpit Studio preview. Cart and customer-session cookies are issued with
SameSite=None; Secure; Partitionedwhen the request comes from the Studio preview frame, so a cart built in the editor persists across reloads instead of resetting on every request. The preview gets its own cookie partition, keeping it separate from your real session on the same shop in the same browser. Top-level storefront traffic is unaffected and keeps its existingSameSiteattributes.
App authors get two new server auto-imports,setManagedCookieanddeleteManagedCookie, which apply this policy. Use them instead of h3'ssetCookie/deleteCookieso an app's cookies work inside the preview — and note that deletions must go throughdeleteManagedCookie, since a delete that omitsPartitionedaddresses the wrong cookie jar and silently leaves the cookie in place.Secureis now derived from the request origin rather than set per connector, which fixes Shopify and Adobe Commerce cookies being dropped by the browser during local development over plain http on a non-loopback hostname.
0.40.1 - 2026-08-06
Patch Changes
- Fix
laioutrrc.jsonapp config being merged into each app module twice. The config was both assigned tonuxt.options[<appName>]and passed toinstallModule, so Nuxt merged it with itself and concatenated every array-valued option. A four-entry Shopifysortingslist arrived as eight and failed the build with a duplicate-key error.
0.40.0 - 2026-08-05
Minor Changes
useConsentStore()exposeshasDecision(), reporting whether the visitor has answered the consent prompt at all. A consent state of "denied" is otherwise indistinguishable from "never asked", which matters wherever that state is passed to a third party applying its own regional default.ConsentAdaptergains an optionalhasDecision?(). The Cookiebot and CCM19 apps implement it, each reporting a saved refusal as a decision rather than as silence. An adapter that omits it always reportsfalse, so a refusal its visitor made is indistinguishable from an unanswered prompt, and consumers forwarding consent to a third party will withhold that refusal rather than pass it on. Granted consent is unaffected — a grant cannot arise from silence.
0.39.0 - 2026-08-04
Minor Changes
- Add a
laioutr://resource locator for addressing a single field inside a project's configuration tree.@laioutr-core/core-types/locatorexportsformatLocator/parseLocatorplus the supporting types (StudioLocator,LaioutrLocator,StudioContainerKind,LocatorPathStep,StudioLocatorView,LocatorParseResult) and theSTUDIO_CONTAINER_KINDSconstant. A locator names a namespace (studiois the only one today), a container (pageVariant,section,sectionRef,globalSection, orblock) by id, a path of object-key or array-item-by-id steps into its props, and optional view coordinates (locale,market,ref) — for examplelaioutr://studio/block/blk_C3/slides[itm_E5]/heading?locale=de.
Both directions also handle a relative form that omits thelaioutr://studio/base:formatLocator(loc, { relative: 'studio' })emits it andparseLocator(input, { relative: 'studio' })accepts it. Without that optionparseLocatortakes absolute input only — a body with no scheme names no namespace, so the caller has to say which one it means.parseLocatornever throws: it returns{ ok: true, value } | { ok: false, error }for every input, including an unsupported namespace, malformed percent-encoding, or empty path segments.
0.38.3 - 2026-08-03
Patch Changes
- Stop the reflect endpoint from serving a previous deployment's section and block
catalog. Its cached reflection is now keyed by build id, so a redeploy is a cache
miss instead of inheriting whatever the last build left behind, and two frontends
sharing one Redis no longer overwrite each other's entry. Cached entries expire
after 12 hours.
Previously the cache entry outlived the deployment that wrote it: a frontend whose cache driver is Redis could hand Studio the old build's component definitions, templates, page types and style tokens after a deploy, and a failed SSR trigger would keep re-persisting that entry rather than refreshing it.
0.38.2 - 2026-07-31
Patch Changes
- Breaking: Stop installing
@nuxtjs/robots.robots.txt, theX-Robots-Tagheader and the route-rulerobotsvalue now come from the@laioutr/app-essentials-seoapp — install it to keep them, and configure them through its app config instead ofnuxt.options.robots.
A frontend with neither that app nor its own@nuxtjs/robotsinstall serves no/robots.txt(a 404 tells crawlers to crawl everything, which is what the previous default content said), and anyrobotskey innuxt.configor in a route rule is silently inert.
Page-levelrobotsmeta tags are unaffected — they come from the page variant's SEO settings, not from the module.
0.38.1 - 2026-07-30
Patch Changes
- Add
aiDisclosuretoMedia, recording whether an asset was fully AI-generated ('generated') or altered by generative AI ('modified'). Available on images, videos and audio; a video'sposterand an audio'scovercarry their own value independently of the parent.
Media-library providers and component resolvers may set it from an authoritative backend signal, and should omit it otherwise: absent means no disclosure is known, not that the asset is human-made. An unrecognised value fails the canonicalMediaparse, so the item is dropped with a server-side warning.
The<Media>component does not render the field.
0.38.0 - 2026-07-29
Minor Changes
- Fill hreflang alternates,
og:locale:alternate,x-defaultand the locale switcher with each locale's own route params instead of the current locale's.
On a page whose slug is translated per locale, the alternate URLs and the locale-switch target previously reused the current locale's params — pointing at URLs that do not exist in the target locale. Where the page type'spageIndexregistration implementslocateand reports a complete per-locale map, each locale now gets its own slug, and locales the page does not exist in are omitted from the alternates rather than guessed at.
Omission follows only from that complete map. Alocatethat resolves just the locale it was called in omits the map entirely, and its pages keep filling every alternate from the current locale's params — a partial map cannot distinguish "no page in this locale" from "did not look", so nothing is dropped on its word.
The lookup runs alongside the page queries and is bounded by a 2s SSR budget. If it breaches, that render falls back to the previous behaviour — every alternate filled from the current locale's params — while the request completes and warms the server cache, so the next render is correct. Page types whose connector provides nolocateare unchanged, as are single-language projects and pages without dynamic params — neither performs a lookup. - Add the
pageIndexorchestr handler kind — one registration per page type that owns that page type's whole page-space.defineOrchestr.pageIndex({ for, label?, batchSize?, list, search?, count?, locate?, cache?, order? }):listwalks the whole page-space in stable order, returning onePageIndexEntryper concrete page ({ params, subject?, meta }) as an array or async iterable; the newpaginate()helper turns a cursor-paged platform API into one. It is called withbatchSize— how many entries the platform serves in a single request, declared once on the registration and defaulting to 100 — and never with a bound, so a walk always caches a complete enumeration.searchanswers a search term with a relevance-ordered top-N, receiving thetermplus atakealready clamped tobatchSize. It is optional: without it a page type still answers search terms, because the runner scans the first 1000 enumerated entries and matches them on title and route params. Implementingsearchbuys relevance ordering and coverage past that scan rather than the capability itself.countsupplies a cheap total for chunked sitemaps and picker totals; consumers degrade when it is absent.locateis a point lookup returningPageIndexLocateResult({ subject?, meta?, locales? }) — a page's route params in every locale it exists in, plus the located page's metadata in the locale the lookup was made in.localescarries a deliberate distinction: present, it is the complete set, and a locale missing from it means the page has no counterpart there, so consumers drop that alternate rather than guess a URL. Absent, it means the connector resolved only the locale it was called in, and consumers fall back to that locale's params. A registration that can answer for one locale must omitlocalesrather than return a single-key map, which would assert absence for every locale it never looked up.cachetunes the enumerate, search and locate tiers independently; walks are cached in cursor-page chunks with stale-while-revalidate and a subject tag index.orderbreaks ties between registrations, higher wins.
Every handler receives the resolvedclientEnv, so a connector scopes its platform reads to the active market withclientEnv.market.id— the same value the runner keys its caches by.
Consumer surface is auto-imported server utils:listPages()enumerates a page type in stable order andsearchPages()returns a relevance-ordered top-N, both as aPageIndexEntryStream(for await,.toArray());countPages()returns a page type's cheap total;locatePage()performs the point lookup; andinvalidateEntity()drops cached chunks referencing an entity.
Thepage-index/listandpage-index/locateendpoints serve these to editor clients under the secret-protected/api/laioutr/namespace.locateis also served ungated atPOST /api/orchestr/page-index/locate, which the frontend itself calls to resolve a page's per-locale slugs — that lookup runs during client-side navigation as well as SSR, so it can never hold the project secret, and it discloses only the route params the rendered hreflang tags publish anyway. Reverse proxies or edge rules that restrict the app's API paths must allow it. Thepage-index/listendpoint validates each enumerated entry on its own and drops the ones that fail with a warning, so a single malformed entry costs one page rather than the whole enumeration. Reflection gains apageIndexmap keyed by page-type token: a key means the type is enumerable,locatemarks the point-lookup capability, andlabel/appLabel/logoUrlcarry the providing app's identity for editor pickers.PageIndexEntry,PageIndexLocateResult,ReflectedPageIndexand the endpoint request/response schemas are exported from@laioutr-core/core-types/orchestr;PageSubjectReffrom@laioutr-core/core-types/common.
Page types without a registration behave exactly as before — an empty stream and one warning. Providers are never required to implement this. - Breaking: Type the
clientEnvfield of thequery-templatesandpage-indexrequest schemas asWireClientEnvrather thanunknown.
Breaking:WireClientEnvnow lives in@laioutr-core/core-types/orchestr, alongside the request schemas that carry it, and is no longer exported from@laioutr-core/orchestr. A handler for theorchestr:client-env:modifyhook takes it from there instead:// before import type { WireClientEnv } from '#orchestr/types'; // after import type { WireClientEnv } from '@laioutr-core/core-types/orchestr';
The resolvedClientEnvthat handlers receive is unaffected and stays in@laioutr-core/orchestr.
Editor clients build the wire payload by hand. While it wasunknown, any object satisfied the type — and because every field ofWireClientEnvis optional, a misspelled key such asmarketidformarketIdalso passed validation, so the request resolved against the default market with no error anywhere. Such a key is now a compile error at the call site, and a request carrying a malformedclientEnvis rejected with400naming the offending path instead of failing further in as a500.
0.37.1 - 2026-07-25
Patch Changes
ClientEnvnow includes adomainfield — the market domain (host, path, language) the current request resolved to. Read it for the request's canonical host instead of assumingmarket.defaultDomain.
The i18n config check now warns when two domains in the same market use the same language, which makes the resolved domain ambiguous — give them region-qualified locales (e.g.de-DEvsde-AT).- Force
noindex, nofollowon content-preview page renders. Preview URLs serve unpublished, secret-gated content and carry the preview token in the URL, so they must never be indexed.PageRenderernow overrides the page's configuredrobots(and anypage-head:resolvehook) whenever a request carries a preview token — whether preview is pending, active, or rejected. - Consent-store consumers registered via
onConsentChangenow receive updates. The store mutated its state ref in place while watching it withoutdeep, so the watcher never fired and consumers reacting to consent changes were never notified. Consent updates now reassign the state, triggering the watcher.
0.37.0 - 2026-07-23
Minor Changes
- Add content preview: a CMS editor opening a storefront URL with
?preview_token=<token>now sees unpublished content, server-rendered, instead of the published page.
The token is verified server-side against the project'spreviewTokenwith a timing-safe compare, and the verdict is what reaches connectors asclientEnv.isPreview. A wrong token renders published content and never throws. Any response rendered for a presented token — granted or rejected — is sent asprivate, no-store, which keeps it out of a shared cache such as a CDN or reverse proxy.
New auto-imported composable:const { enabled, status, state } = useContentPreview(); // status: 'off' | 'pending' | 'active' | 'rejected' // enabled: true only once the server has verified the token — safe for `v-if` // enabled is writable, so `enabled.value = false` still exits preview
Two new hooks:frontend-core:content-preview:resolve-token— a bail hook over the token source. Setresult.valueto drive preview from a cookie or request header instead of the query parameter. Handlers must be synchronous and registered from a plugin withenforce: 'pre'.frontend-core:content-preview:changed— fires after a preview transition with the newenabledvalue. frontend-core already refreshes Nuxt data and the orchestr store; use this to invalidate your own caches.
AddsPOST /api/frontend/preview-verify, an unauthenticated endpoint that answers{ granted: boolean }for a presented token. It is a UI hint only — authorization for real data is the gate that runs on every orchestr request regardless.
Media-library requests (Cockpit's asset browser) now run with the project's default market and language instead of a placeholder{ locale: 'en', currency: '' }. Providers that relied on the empty currency triggering their own fallback will now receive the default market's real currency. Media requests are never preview. - Add the content-preview token to the two contracts that carry it.
RcProject.laioutr.previewToken?: stringis the project's content-preview token, read fromlaioutrrcby every deployed frontend. It is separate fromprojectSecretKeyand separately rotatable, because it is pasted into CMS preview-URL templates. It reaches a frontend at deploy time, so rotating it in Cockpit has no effect until the project redeploys.EditorChildProps.previewToken?: stringlets Cockpit push the token to the Studio iframe when an editor turns content preview on. Absent orundefinedmeans preview is off.
0.36.0 - 2026-07-20
Minor Changes
- Breaking: The
aimetadata on section and block definitions is now a typedAiComponentMetadataobject with exactly two optional fields —description(agent-facing facts that don't fit the picker-facingstudio.description) andexamples(a worked slot/block composition for complex multi-block sections). The previous free-form{ label, description }shape, the co-locatedai-descriptions/*.mdfiles, and theaiDescription()helper are removed. An absentaiobject is the normal state:studio.descriptionplus the field/slot schema carry the load for agents, and all built-in section/block descriptions have been enriched with the facts that previously lived inaimetadata. Prescriptive guidance fields (useWhen,avoidWhen,constraints,neverWith,pairsWellWith,typicallyFollowedBy,supports, …) are gone — evals showed they measurably degrade agent page composition.// before ai: { label: 'Button', description: aiDescription('BlockButton') } // after — most definitions: // (no ai key at all; put facts in studio.description) // after — only where a fact doesn't fit the picker text or an assembly is non-obvious: ai: { description: 'Currently a progress display only — it does not yet fetch the next batch.', examples: 'gallery slot: BlockProductMediaGallery; content column: BasicInfo, PriceInfo, CartButton in on-page order.', } - Add the
DateTimecommon value type — a timezone-qualified ISO 8601 instant (z.iso.datetime({ offset: true })), completing the temporal set next toCalendarDate,Time, andDuration. - Add optional
studio.package,studio.kit, and top-levelaimetadata to section and block definitions.
0.35.1 - 2026-07-14
Patch Changes
- Fix legacy media-library providers being omitted from Cockpit by registering their v2 provider and descriptor with the correct registry arguments.
0.35.0 - 2026-07-14
Minor Changes
- Breaking: Media libraries are now connected as an Orchestr integration facet. A connector declares static capabilities (search, tags, folders, sorts, upload transfer) and uses opaque-cursor pagination, explicit type/tag filtering, optional folder navigation, and proxied or staged upload with per-file results. Define one on the app's Orchestr builder instead of the standalone factory:
// Before export default defineMediaLibraryProvider({ name, label, iconSrc, list, upload }); // After export default defineShopify.mediaLibrary({ capabilities: { search: true, folders: false, sorts, upload: { transfer: 'staged' } }, list, createUploadTargets, finalizeUploads, });defineMediaLibraryProvider()still works as a deprecated shim — existing connectors keep registering without a rewrite, in a degraded mode (no folders, no staged upload, no declared sorts).ProjectFrontendContext.mediaLibrariesnow carries descriptors{ id, label, iconSrc, capabilities }.
The Shopify connector uploads via staged targets and blocks until each file isREADYbefore returning it (one failed file no longer sinks the batch). The Shopware connector gains folder browsing over the real media-folder tree.
This frontend-core version is the threshold for the CockpitmediaLibraryV2capability gate; the Cockpit media picker is updated separately to speak the new contract.
Folder browsing is folded into the singlelistmethod:MediaListResult.folderscarries the queried location's subfolders on the first (cursorless) page; the separatebrowseFoldersmethod andmedia-foldersroute are removed. Every media source now carries an optionalorigin({ libraryId, externalId? }), stamped by the.mediaLibrary()wrapper, which also validates all adapter output at the trust boundary (canonical Zod parse, URL-scheme guard — including nested poster/cover images — capability/response agreement) and logs a server-side warning for every dropped item. Browse items may carry a transientstatus(processing/failed) surfaced in the picker grid.
Media-library handlers now receive the per-request context built by the app'sextendRequestinitwares as their second argument —list(query, ctx)— so adapters use the initware-provided clients instead of constructing their own.MediaQuerygainsscope: 'folder' | 'all'to distinguish a whole-library search from browsing the root level (on Shopware, root holds only unfiled assets), and both bundled adapters now honorMediaQuery.typeserver-side. - Add the
@laioutr-app/cmsmedia-library connector and theRcProject.config.cdncontainer it reads. The connector implements the shipped media-library interface againstapps/cdn-api: cursor browse with folders, staged upload, and baseURL-free Cloudflare providers (image + video poster) on the per-project delivery host.RcCoreConfiggains an optionalcdn: { key, deliveryHost }member (client-stripped via the existingconfigsanitisation). - Video sources now support a
focalPoint, mirroring image sources. The built-inMediaVideorenderer applies it asobject-position(per viewport, withcenter centeras the fallback) so the important region stays in frame when the video is cropped byobject-fit: cover.
0.34.0
Changed
- Breaking: Media libraries are now connected as an Orchestr integration facet. A connector is declared with
defineX.mediaLibrary(...)on the app's Orchestr builder instead of the standalonedefineMediaLibraryProviderfactory. Identity (id,label,iconSrc) is derived from the builder's.meta(); the connector declares staticcapabilities(search, tags, folders, sorts, upload transfer) and its handlers receive the app's per-requestctxas a second argument. Browsing moves to opaque-cursor pagination (MediaQuery→MediaListResult) with folders folded intolist, an explicittypefilter, and a browse-timestatus; upload gains a staged (direct browser→backend) path alongside proxied, both returning per-file results.ProjectFrontendContext.mediaLibrariesnow carries{ id, label, iconSrc, capabilities }descriptors. See Media and Media Library. - Every
Mediasource gained an optionalorigin({ libraryId, externalId? }) recording the producing library and its stable asset id. It is stamped automatically when an asset is picked from a media library, is management-plane only (the renderer never reads it), and is additive — media stored before it existed remain valid. See Media.
Deprecated
defineMediaLibraryProvider(...)is deprecated in favour ofdefineX.mediaLibrary(...). It keeps working as a compatibility shim so existing connectors register without a rewrite, in a degraded mode (no folder navigation, no staged upload, no declared sorts, no server-side type filtering), and logs a one-time warning at registration.
0.33.1 - 2026-07-06
Patch Changes
content_alignmentfield values that fall outside the field's axis-derived set are now healed when resolving render props, instead of passing through. A value already in the set is unchanged. When a field'saxiswas changed after the value was stored, the stored value is projected onto the new axis rather than discarded:- Restrict (
both→ single axis): the matching component is kept —top-leftrenders astopon a vertical field,lefton a horizontal field. - Widen (single axis →
both): the value is paired with a neutralcenterfor the missing axis —topbecomestop-center,leftbecomescenter-left.
Only a value with no analog on the new axis (an axis swap, e.g.topon a horizontal field) or a malformed value (e.g.diagonal) clamps to the field's fallback alignment (center/center-center). Previously any out-of-range value was discarded and replaced with the fallback, silently losing alignment intent the stored value still carried.- Restrict (
0.32.1 - 2026-06-30
Patch Changes
- Resolve SEO title/description/robots placeholders against a setup-time snapshot of the page's queries instead of the live, route-reactive query map.
useSeoMeta's getters are evaluated during SSR head serialization — after render and outside Nuxt's async context — so reading the live map there rebuilt every query's wire request for nothing (the wire request is only a result-lookup key). The head now reads a stable snapshot; sections keep the live map so client-side filter/sort/pagination still re-fetch.
0.31.0
Added
- Frontend Core: Pages now render referenced global sections. A referenced global section is dereferenced into the page, its queries are merged into the page's query set, and its configuration is wired through at render time — so a section shared across pages renders consistently wherever it is referenced.
- Core Types:
CalendarDatevalue type — an ISOYYYY-MM-DDcalendar date (no time, no timezone), exported from@laioutr-core/core-types/common. Use it for whole-day values such as a location's opening/reopening date.
Changed
- Core Types: Aligned
RcGlobalSectionslots and queries toRcDictionary, and added an optionalstudio.description.
0.30.3
Added
- Frontend Core: Render pipeline now supports
RcPropValueEntityProperty. Seeds are gathered, query paths are resolved via the new sharedresolveEntityPathhelper, and resolved values are coerced throughcoerceFieldValue— so e.g. a string URL bound to a media field becomes aMediaobject. The dynamic-string render branch now also routes throughresolveEntityPath, unifying the two query-bound paths. - Frontend Core: Reflect endpoint now exposes
installedApps: Record<string, AppRuntimeMeta>— everyregisterLaioutrAppcaller (including frontend-core itself) keyed by name, with itsversionandpageWrapper. Backed by a new server-only virtual file#laioutr/installed-apps, populated lazily fromlaioutrAppRegistry.getAllMetas()so apps registered later in module setup are still captured. - Frontend Core:
frontend-core:link-resolver:resolveis now a filter hook. It runs after a link is resolved, withresult.valuepre-seeded with the resolved URL or path. Handlers receive the resolved value and may transform it (e.g. append query params) for any link type, and the value is threaded across multiple handlers. Existing handlers that overwriteresult.valuekeep working unchanged. - Core Types:
RcPropValueEntityPropertyfor query-bound prop values — a new variant on theRcPropValueunion that lets a single prop read its value from a property of a query-result entity (e.g.{ type: 'entity-property', queryId: 'q1', path: ['components', 'base', 'image'] }). The compositepathuses the samecomponents.*/links.*form as string-template references. - Core Types:
installedApps: Record<string, AppRuntimeMeta>onProjectFrontendContext— apps registered in the deployed frontend viaregisterLaioutrApp(including frontend-core itself). Used by the cockpit to gate features that require a specific framework/app version.
Changed
- Frontend Core:
rcPropValueToRendernow treats unknownRcPropValue.typevalues as "no value" (returnsundefined) and emits a deduplicatedconsole.warn, instead of leaking the raw value object to downstream renderers. Lets newer studio configs degrade gracefully on older frontend-core deployments rather than crashing. - Core Types: Lifted
RenderQueryReference.queryPathfromstringtostring[]and added anentity-propertyvariant to theRenderQueryReferenceandRenderQueryLoadSpecSeedunions (internal render-pipeline types, consumed only by@laioutr-core/frontend-core).
Fixed
- Frontend Core:
frontend-core:link-resolver:*andfrontend-core:page-renderer:select-page-varianthooks now actually take effect — handler-setresult.valueis read synchronously. Previously the result was read before Nuxt's deferred handlers ran, so every registered handler's output was silently dropped. - Core Types: Fixed silent registry eviction in
applyZodFixthat caused id-less reflection output (e.g.MediaImage,MoneyAmount,Link) on Vercel-deployed Nuxt apps.applyZodFixpreviously evicted the existing registry entry when a schema with the same id was registered a second time; in the dual-bundle case (server bundle +@vercel/nft-traced copy of@laioutr-core/core-types) this leftzodToJSONSchemaunable to find the schema's metadata, so reflection inlined canonical schemas without anidor$refand Studio consumers misclassified Media/Money/Link properties as plainobject. New behavior: merge the new meta into the existing entry and store the merged record against both schema instances. HMR additive edits todescription/title/examplesstill take effect; removing a meta field requires a dev-server restart (rare).
0.30.2
Added
- Frontend Core: URL redirects configured for the project are now honored at runtime. Visiting a redirect source path — exact match or
:parampattern — sends the user to the configured target, withisPermanent: truereturning 301 andisPermanent: falsereturning 302. Works for direct hits, hard reloads, and SPA navigations via<NuxtLink>orrouter.push, including on storefronts that define a customcore/404page. Query strings are preserved; targets can be absolute URLs for external redirects.
0.30.1
Added
- Frontend Core: New
laioutr:beforeModuleRegisterNuxt hook fires before the ui-kit module registers each upstream module, with{ name, key, options }. Consumers can mutateoptionsto override defaults applied byregisterModule. - Common Types:
Mediagained anaudiovariant.MediaAudiocarriessources: MediaSourceAudio[], an optionalcover(album art / podcast cover), and optionaltracks. The newMediaTextTracktype backs timed text tracks (subtitles, captions, chapters) for both audio and video, andMediaVideonow acceptstracksplus astreamingdelivery format (progressive | hls | dash). Studiomediafields can restrict to audio withallowedTypes: ['audio'].
Changed
- Common Types (breaking):
MediaVideo.previewwas renamed toMediaVideo.poster, matching the HTML<video poster>attribute. Rename the field on everyMediaVideoyou construct or read. - Common Types (breaking): the
Mediaunion now includesMediaAudio, somedia.typecan be'audio'. Exhaustiveswitch (media.type)blocks without adefaultbranch must add an'audio'case.
Fixed
- Frontend Core: Section
slotsprop now retains structural typing of block props at consumer call sites. The previous wrapper used deepSimplifyDeep, which past a certain schema depth tripped TypeScript's 50-level instantiation limit and surfaced asTS2589: Type instantiation is excessively deep and possibly infinitein sections that mapped over their slot blocks. BoundedSimplifyis now applied at the slot-map / block /propsboundaries. Sections that previously needed anas unknown as ReadonlyArray<{ props: Record<string, any> }>cast onprops.slots.*can drop the cast and accessblock.props.<field>with full type inference. No runtime or API change.
0.30.0
Added
- Common Types: Studio field and fieldset definitions accept an optional
if: SchemaCondition— a JSON expression (typed via@laioutr/expression) that hides the control in the Studio sidebar when it evaluates to a falsy value. The stored value is kept and still passed to your component at render time; only the sidebar control disappears. Example:if: ['==', ['get', 'background'], 'custom'].
0.29.0
Added
- Frontend Core: 8 missing discriminated event types added to
tracking.types.ts—RemoveFromCart,AddToWishlist,RemoveFromWishlist,ViewCart,AddShippingInfo,AddPaymentInfo,Login,SignUp. Trackers intrackingActions.tsnow construct the correct types instead of falling through to mismatched ones (e.g.ViewItemforADD_TO_WISHLIST), which previously broke theAnalyticsdiscriminated union silently. - Frontend Core: Re-exported the
DefinitionToPropstype from#frontend/typesso consumers can derive prop types from a section/block definition without reaching into internal paths.
Changed
- Core Types (breaking): Updated
Media,Swatch, andcoerceFieldValueto align with the surface-tone type changes in the UI family (BackgroundBrightness→SurfaceTone). Consumers reading these types should treat the colour-mode field as'light' | 'dark' | 'bright'.
Fixed
- Frontend Core: Reordered the
undefinedcheck invalidateI18nConfigto happen before indexingmarket.domains.FieldDefinitionToPropgeneric constraint widened fromStudioFieldDefinitiontoBaseFieldDefinitionBaseso the type-parameter chain is consistent withFieldDefinitionToTypeandFindFieldWithName.
Removed
- Frontend Core: Removed the unused
InvisibleBlockcomponent. It had no source consumers — only auto-generated.nuxt/components.d.tsreferences that regenerate on the next build. Its placeholder UI was leftover from an earlier editor experiment and was not used anywhere in the runtime tree.
0.28.15
Changed
- Core Types: Use
z.object({ ...base.shape, ... })instead ofbase.extend({ ... })for schema composition so the JSDoc zod-meta plugin resolves field descriptions correctly.
0.28.14
Changed
- Core Types: Wired
urlAliasandisRootend-to-end from RC page config through the orchestr store to client-side URL generation. RefactoredQueryParamssoisRootis the single source of truth for root-level URL params — callers no longer need to pass an empty prefix.
Fixed
- Frontend Core: Fixed
RemoteRendernot caching page data across navigations, preventing redundant data fetches when returning to a previously visited page.
0.28.13
Fixed
- Frontend Core: Fixed
PageRenderercrashing when the Nuxt instance is unavailable by removing the hook registration guard safely. - Frontend Core: Fixed the page-variant selection hook in
PageRenderernot correctly resolving the selected variant in all cases. - Frontend Core: Fixed incorrect port displayed in the CLI startup banner.
0.28.12
Added
- Frontend Core: New Nuxt hook for page-variant selection, allowing apps to intercept and override the active page variant during rendering.
0.28.11
Added
- Orchestr: Exported
OrchestrBuildertypes so apps can re-export their builders with correct TypeScript types.
0.28.10
Added
- Frontend Core: Cross-locale redirect middleware that redirects users to the correct locale URL on entry.
- Frontend Core: Shared hreflang link utility with fixes for missing
x-defaultpath prefix. - Frontend Core: Longest-prefix domain matching for multi-market domain resolution.
- Frontend Core: Warning logs for host-sharing conflicts and unknown market/locale fallbacks.
Fixed
- Frontend Core: Fixed SSR protocol detection in
linkResolverto correctly generate absolute URLs. - Frontend Core: Language fallback in
buildI18nConfignow resolves correctly when a locale has no explicit configuration.
0.28.9
Fixed
- Frontend Core: Fixed duplicate section templates in Studio by switching the template registry from an array to a Map, preventing re-registration on repeated SSR renders.
- Frontend Core: Wired mock style tokens into the reflect API so the Studio receives color and icon data instead of empty objects.
- Frontend Core: Fixed
useRoute()returning stale route data in Studio preview. The preview now emitspage:finishafter each navigation to keepuseRoute()current. - Kit: Fixed
useRoute()returning stale route data in studio preview. In preview mode there is no<NuxtPage>, so thepage:finishhook that syncs Nuxt's internal route ref never fired. The preview now emitspage:finishafter each navigation to keepuseRoute()current.
0.28.8
Fixed
- Frontend Core: Fixed
useRoute()in Studio preview mode.
0.28.6
Fixed
- Frontend Core: Fixed reflect API returning 503 on Vercel by using
globalThisas the data transport between the SSR plugin and API handler. Adds a persistent cache fallback for cold starts, a dedicated lightweight SSR trigger route (/_laioutr/reflect), and improved error logging.
0.28.5
Fixed
- Frontend Core: Fixed "chain is not iterable" crash when no markets are configured by adding the missing
localeChainto the fallback language object.
0.28.4
Fixed
- Frontend Core: Inlined core-types into Nitro bundle for Vercel compatibility.
0.28.3
Fixed
- Frontend Core: Fixed reflect API route for Vercel deployment.
0.28.2
Fixed
- Core Types: Replaced wildcard package exports with static exports to fix Vercel builds.
0.28.1
Fixed
- Frontend Core: Fixed Orchestr import path.
0.28.0
Added
- Frontend Core: Field-value coercion — automatic conversion between field types (text→media, text→link, text→number, number→text).
- Frontend Core: Shadow
nuxt-i18ncomposables for seamless multi-market integration. - Frontend Core: Route aliases for market domains in Studio preview mode.
- Frontend Core: Send proper error 404 responses on not-found pages.
- Core Types: Enforce field translatability in Studio
setStaticProp. - Core Types: Enable JSON Schema meta and replace
zodAswithz.ZodType. - Core Types: Split field definitions into studio and system categories.
- Core Types: Make
RcPage.pathoptionally non-localized for pages that don't need per-locale paths. - Core Types: Multi-market render types:
RenderLanguage,RenderMarket,RenderI18nConfig, pluslocalizedPathsandmarketIdsonMetaPage.
Changed
- Frontend Core: Removed
ALL_LOCALESwildcard in favor of explicit locale codes throughout the frontend codebase. - Frontend Core: Improved route resolution during navigation.
- Core Types (breaking): Removed the
ALL_LOCALES('*') wildcard. ThelocaleChainno longer includes'*'as a tail element, andnormalizeLocalizedPathsno longer prefers the'*'key as fallback. All localized values must use concrete BCP 47 locale codes. - Updated Orchestr, Kit and Core Types to
0.28.0.
Fixed
- Frontend Core: Correct locale-switching behavior in Studio preview.
- Frontend Core: Do not assume no-prefix for default-domain in markets.
0.27.3
Added
- Frontend Core: Multi-market i18n support using nuxt-i18n:
buildRouteswith alias generation and collision detection for localized paths.resolveMarketFromRequestfor host- and path-based market resolution.buildI18nConfigtransform andvalidateI18nConfig(dangling ref and collision detection).- Utilities:
fillParams,toDevHost,getExonym. - Chain-based
unlocalizeandresolvePagePath. - Core Types: Render i18n types (RenderLanguage, RenderMarket, RenderI18nConfig, localizedPaths, marketIds).
Changed
- Orchestr: Migrated from async iterable to ChunkStreamer on the server for streaming.
0.27.2
Added
- Frontend Core: Added a Cockpit link to the CLI banner so developers can jump directly from the dev server to the corresponding project in Cockpit.
Changed
- Updated Orchestr, Kit and Core Types to
0.27.2.
0.27.1
Changed
- Frontend Core: Improved types for tracking integrations and added the missing vue-router connection for tracking.
- Updated Orchestr, Kit and Core Types to
0.27.1.
0.27.0
Added
- Core Types:
createEntityComponentTokenFactoryhelper.
Changed
- Orchestr: Queries now respect all query aliases on navigation, ensuring correct query reuse when navigating between pages.
- Frontend Core: Updated dependencies to the
0.27.0release line (Orchestr, Core Types, Kit).
0.26.1
Changed
- Frontend Core: Consent adapter quality-of-life improvements (easier integration and better ergonomics for consent adapters).
- Updated Orchestr, Kit and Core Types to
0.26.1.
0.26.0
Added
- Frontend Core: Respect
queryReference.linkwhile resolving query fields so linked queries behave correctly. - Core Types: JSON field type.
Changed
- Orchestr: Removed input from links and allowed passing entities from links, making link handlers more flexible.
- Core Types: Implemented the most common types as proper TypeScript types.
- Updated Core Types and related dependencies to
0.26.0.
0.25.0
Added
- Core Types: Optional
descriptionfield onpageTypeToken. - Core Types:
WellKnownComponentTagvalue'Blog'.
Changed
- Frontend Core & Orchestr: Dependency updates to align with Core Types
0.25.0and internal improvements.
0.24.0
Changed
- Frontend Core & Orchestr: Dependency updates around Core Types
0.24.0with internal refinements. - Core Types: Separated
core-typesfrom thecanonical-typespackage.
0.23.1
Added
- Frontend Core: Added a CLI banner to improve the developer experience when starting the frontend.
Changed
- Updated Orchestr, Canonical Types and Kit to
0.19.1/0.23.1.
0.23.0
Added
- Frontend Core: Respect default query limit coming from
RcQueryLoadSpec, so frontends honour query default limits defined in RC.
Changed
- Orchestr: Added a
pathproperty to error chunks and improved default query limit handling andshouldLoadbehaviour in query handlers. - Updated Canonical Types and Kit to match the new query behaviours.
0.22.2
Added
- Frontend Core: Allowed the link resolver to handle string links, making it easier to work with plain URLs in link fields.
Changed
- Orchestr & Canonical Types: Internal updates around
0.20.0/0.18.0.
0.22.1
Fixed
- Frontend Core: Links with references are now case-insensitive to their
type, preventing subtle bugs when reference types differ only in casing.
0.22.0
Added
- Frontend Core: Properly implemented the link resolver and moved core page-type registration to a plugin so page types are registered consistently.
Changed
- Orchestr & Canonical Types: Updated to support the improved link resolver and page-type handling.
0.21.1
Changed
- Frontend Core & Orchestr: Dependency updates around error handling and tracing to
0.19.0/0.16.0.
0.21.0
Added
- Frontend Core: Exported i18n formatters so projects can reuse Laioutr’s measurement and money formatting utilities.
Changed
- Orchestr: Added missing client-side action hooks and
passthrough.require, improved tracing and component-cache behaviour.
0.20.6 – 0.20.1
Changed
- Frontend Core & Orchestr: Series of dependency and cache-related improvements (passthrough caching, component cache, cache-key fixes, and Dev-only LRU cache for orchestr-store).
0.20.0
Added
- Frontend Core: Added
projectSlugto the frontend-core config and a provisional link-resolver that later evolved into the current link resolver feature.
Changed
- Orchestr: Introduced a cache-clear API endpoint, the ability to pass components directly from query handlers, and improved passthrough storage by token-string.
0.19.x – 0.18.x
Added
- Frontend Core: Implemented dynamic-string references in string props and support for queries in array-items and object fields.
- Orchestr: Introduced the initial caching mechanism, stable hashing for the orchestr Pinia store, remote
QueryTemplates, and basic request tracing / summary support.
Changed
- Frontend Core & Orchestr: Multiple dependency updates around Canonical Types, Kit and Logger to support the new query/template/caching features.
0.17.x – 0.14.x
Added
- Frontend Core: Implemented remote
QueryTemplates, added a catch-all page, and improved Studio refresh compatibility. - Orchestr: Added support for remote
QueryTemplates,extendRequest, preview flags inclientEnv, and better template provider reflection.
Changed
- Frontend Core & Orchestr: Several internal refactors, type-guard moves, and dependency upgrades to align with Canonical Types and Kit.
0.13.0 and earlier
Added
- Initial public versions of Frontend Core and Orchestr, including:\n - Base page/section rendering pipeline.\n - Early section definitions and demo setup.\n - First integration of media library handling and documentation generation from canonical types.\n - Migration of base components to the UI Kit.\n - Shopify demo and early ecommerce flows.\n
- Kit: Media Library upload handling, and improved documentation generation from canonical-types.
Changed
- Iterative improvements to configuration, query handling, and orchestr integration as the frontend product matured towards its current architecture.