inmake DOCS
Docs / Generative / Site structure

Site structure

The "site-structure" rule fragment — composed into the CRM & Studio agents' prompts and served as drop-core://rules/site-structure.

SITE STRUCTURE: this document is one page of the store's web site. [localesLine — filled in at runtime]

  • The site config is setup/web: published, index/header/footer (each a {collection, document} reference to a data doc), and shell.menu (the navigation items, each {label?, page|href}: href is a verbatim URL; page is either a site path string or a DOCUMENT POINTER — the link envelope {type:"link", collection, document, hash?} (or the legacy {collection, document}) — that the renderer resolves to the target's public URL in the page's locale; label may be omitted for a document page, the target's localized title fills in; an unresolvable item is dropped. Write document pointers, never hand-typed paths, for pages of this store).
  • setup/web.headTags is a raw-HTML string injected into EVERY page's <head> — the only channel for a SHARED <link rel="stylesheet">, <script src>, web-font or analytics tag (a page template renders into <body> and cannot touch <head>). Store-file links in it MUST be site-relative /assets/{storagePath} paths (never absolute storage.googleapis.com URLs) and it must carry NO raw <style> blocks. Edited via updateStoreDocument (read the existing string, append, write it back whole); the mechanics + format rules live in the assets rule. If you are a page editor without updateStoreDocument, tell the user to set it in Web setup, or to ask the project Assistant.
  • setup/web.footerTags is its end-of-<body> twin: raw HTML emitted on EVERY page after the footer region and the DROP_* globals, ahead of the page/element scripts — the place for third-party analytics, chat widgets and tracking pixels that should not hold up the first paint (a first-paint bootstrap still belongs in headTags). Same format rules as headTags (site-relative /assets/… links, no <style> blocks), same edit mechanics, and gated by consent exactly like headTags.
  • COOKIE CONSENT is a PLATFORM feature, switched on per store in setup/web.consent ({enabled: true, layout?, position?, categories?, policy?, gateHeadTags?, blockUnknown?}; OFF by default, the operator's call in Settings → Web → Cookie consent). NEVER hand-roll a cookie bar, a consent banner or a "we use cookies" strip into a template or an element — when the user asks for one, set setup/web.consent.enabled: true (updateStoreDocument) and tell them the services list is scanned in the console. With consent on, the renderer GATES setup/web.headTags and footerTags: third-party analytics / marketing / embed tags become type="text/plain" data-drop-consent="{category}" and run only after the visitor grants that category (same-origin code and unclassified inline bootstraps are never gated); page/element code that must wait for consent listens to document.addEventListener('drop:consent', e => e.detail.granted) or reads window.DropConsent.get().granted, and any element with a data-drop-consent-open attribute reopens the settings panel. There is NO floating settings button — the "Cookie settings" link lives in the site FOOTER: when you build or edit a footer, include <button type="button" data-drop-consent-open hidden></button> (EMPTY and hidden — the platform un-hides it and fills in the localized consent.reopen label only when the bar is on; style it like the footer's other links). Texts come from the consent translation group (consent.title, .body, .accept, .reject, .settings, .save, .policy, .reopen, .{category}, .{category}_desc) over built-in cs/en defaults.
  • ROUTING — the published site serves four kinds of URL: / (the index doc); developer-defined static routes; /ref/{collection}/{document} — ANY data doc rendered directly, under the reserved /ref/ prefix; and store SLUGS — any other path resolves to the doc whose seo.slug (or alias) matches (e.g. a product with seo.slug: "mobile-stand" is served at /mobile-stand) — plus COLLECTION ROUTES: a collection whose setup/data row has route.enabled serves its documents at /{segment}/{slug} too (the segment = the collection's locale slug or its key, e.g. /recepty/bruschetta; that URL is the canonical one, the flat /bruschetta keeps working), and the bare /{segment} renders the row's route.index page unless a document already owns that slug — plus CATEGORY ROUTES, one level below a routed collection: a document whose seo.category names one of the row's route.categories serves at /{segment}/{category}/{slug} (canonical, e.g. /recepty/polevky/gulasova; the category segment is its locale slug or key, its aliases 301 to it), and the bare /{segment}/{category} renders that category's index page unless a document of the collection owns that slug. A document's seo.scope (both default | root | collection) decides whether its slug answers at the root, under the collection route, or both — the same slug may live in several ROUTED collections (each has its own registry), while root slugs are unique per LANGUAGE (a slug belongs to the locale document that declares it — the cs and en pages may share one or use their own). The platform keeps the slug registry in sync from seo.slug / seo.aliases: never write or delete a registry entry, and to change a URL change the document's seo.slug — the old URL then redirects to the new one by itself. So a page CAN link to another document's own page — the right way is a kind:"link" field holding the envelope {type:"link", collection, document} (the renderer emits the localized, routed URL; see the field-kinds rule); {{route "{collection}" slug}} + the reference-route fallback stays for documents reached through a collection/list reference (see LINKING below).
  • LANGUAGE IN THE URL — a store either keeps the visitor's language in a cookie (one URL serves every language) or in the URL (/about = the default language, /en/about another one — setup/web.localeUrl, set in the console). Templates work in both modes unchanged as long as every page link comes from a link field, {{route}}, a menu item or {{#each alternates}} — the renderer adds the language prefix itself (literal root-absolute hrefs like href="/kontakt" get it too). NEVER hand-build a /en/ prefix, never write the language into a slug, and in scripts build links with DROP_BASE + (DROP_LOCALE_BASE || '') + path (or DropElements.pageHref(path) where the store's library runtime defines it). A LANGUAGE SWITCHER is the store's language-switcher element where its library has one, else {{#each alternates}}<a href="{{href}}" hreflang="{{hreflang}}">{{label}}</a>{{/each}} — each entry is THIS page in one store language, current marks the served one; in cookie mode the switcher must set the drop_locale cookie and reload instead of following the link.
  • SITEMAP & HREFLANG are PLATFORM output — never hand-write a sitemap file, an xhtml:link, or <link rel="alternate" hreflang> tags into headTags or a template. /sitemap.xml is built from setup/web.sitemap ({enabled?, collections?: string[], exclude?: [{collection?, document}]}): the homepage, static routes and every pages document are ALWAYS listed (pages needs no entry in collections); collections adds other routed collections' sub-sitemaps; exclude hides single documents (collection defaults to the site's default collection, normally pages) — the page keeps serving. The homepage is listed once, as / (never under its slug); the 404 and maintenance pages never. With the language in the URL every sub-sitemap splits per locale (/sitemap-recipes.cs.xml) and the root is an index. Sitemaps carry plain URLs only — the renderer puts the language versions (hreflang) in each page's <head>, the one method Google needs. Edit these keys only when the user asks (updateStoreDocument on setup/web; arrays are replaced whole — read first, send the full list).
  • To build a navigation bar / in-page ANCHORS from the site menu: readStoreDocument("setup/web") FIRST, then read the doc(s) it references (the header doc, or the shell.menu targets), derive the label→path/href pairs, and render them as links/anchors in writeWebTemplate. Do NOT invent menu items — use what the setup + referenced docs actually contain.
  • TEMPLATE CHANGES ARE PATCHES: for a layout/design change that touches less than roughly half of a template, call writeWebTemplate with edits — a list of {field: html|css|js, search, replace} where every search is copied VERBATIM from the template source you read (the smallest span that occurs exactly once; search: "" appends, replace: "" deletes). A span that does not match is refused with the closest lines — fix the span; after two misses send the full html + css. The full bodies are only for a rewrite. Never send both.
  • FAVICON: set setup/web.favicon (a raster image value — the standard sizes are generated as variants) and/or setup/web.faviconSvg (an inline IconValue). The renderer injects the <link rel="icon"> tags ahead of headTags AND serves the /favicon.ico route — never hand-write a favicon <link> into headTags.
  • The site shell provides NO <main> landmark — every page template carries its own: make the page-root wrapper a <main class="…"> element.