Pagebuilder-Anbindung (Dev)
Die Vorschau im tibi-admin ist nicht deine Website.
Sie rendert vereinfachte Vorschau-Blöcke in einem Shadow-DOM.
Die echte Website baut der BlockRenderer im Starter per SSR (siehe Website mit SSR).
Eintrag (blocks[]) → Registry (Vorschau) → Shadow-DOM im EditorEintrag (blocks[]) → BlockRenderer.svelte → echtes HTML im ProjektRegistry-Vertrag
Abschnitt betitelt „Registry-Vertrag“Die Registry ist eine JS-Datei mit einem Block-Namen pro Schlüssel.
Jeder Block liefert render, dazu update und destroy im Handle sowie ein css-Array.
Pfad steht in meta.pagebuilder.blockRegistry.file (Feld oder Collection).
function defineBlock(renderContent) { return { render(container, row, context) { const el = document.createElement("div") renderContent(el, row, context) container.appendChild(el) return { update(nextRow, nextContext) { renderContent(el, nextRow, nextContext) }, destroy() { el.remove() }, } }, css: ["/assets/mock/corporate-blocks/blocks.css"], }}
export default registryexport { registry as blockRegistry }| Teil | Vertrag (BlockMount.ts, load-registry.ts) |
|---|---|
| Export | default oder blockRegistry als BlockRegistry-Map |
render(container, row, context) |
Baut das DOM, gibt { update, destroy } zurück |
update(row, context) |
Neue Daten bei gleichem Typ — kein DOM-Neuaufbau |
destroy() |
Räumt auf (Listener, Element entfernen) |
css |
Array mit Stylesheet-URLs, wird vor dem Mount vorgeladen |
| Fehlt der Typ | render() gibt false zurück → Blueprint-Karte statt Vorschau |
Laden ist lazy und pro URL gecacht (loadBlockRegistry, load-registry.ts:68-94: Map-Cache pro aufgelöstem Specifier; Import via dynamischem import(), vor esbuild versteckt).
Schlägt der Import fehl oder fehlt der Export (default/blockRegistry), zeigt der Editor den Blueprint-Modus — kein Fehler, nur keine Vorschau.
Registry-Pfad-Auflösung in drei Schritten (resolveBlockRegistrySpecifier, load-registry.ts:18-35):
- Quelle: Feld-
meta.pagebuilder.blockRegistry.file, Rückfall auf Collection-meta.pagebuilder…— jeweils String oder{ eval }-Ausdruck (ausgewertet perresolveMetaEvalmit Namespace und API-Basis). - Ergebnis durch
resolveProjectAssetSpecifier(Projekt-Basis) — siehe Tabelle unten. - CSS-Präzedenz (
resolveBlockCssTransform,load-registry.ts:50-61): Block-Definition → Feld-meta.pagebuilder.cssTransform→ Collection → Defaulttrue(undefinedheißt „nicht gesetzt”, nichtfalse).
| Specifier | Auflösung (project-asset-specifier.ts:17-50) |
|---|---|
https://…, http://…, //…, data:, blob: |
Unverändert (extern/inline) |
_/… |
Projekt-relativ zur Namespace-Basis (empfohlen) |
/_/… |
Alias für projekt-relativ (rückwärtskompatibel) |
/… |
Host-absolut, bleibt wie geschrieben |
relativ (assets/x.js) |
Gegen die Projekt-Basis aufgelöst |
{ eval: "…" } |
JS-Ausdruck vor dem Specifier ausgewertet (resolveMetaEval, load-registry.ts:26-31), Ergebnis läuft danach durch dieselbe Tabelle |
CSS-Umschreibung
Abschnitt betitelt „CSS-Umschreibung“Dein Block-CSS läuft durch transformPagebuilderCssLenientAsync (pagebuilder-css-transform.ts).
Grund: Das CSS landet isoliert im Shadow-DOM, nicht im Seiten-<head>.
| Umschreibung | Warum |
|---|---|
@media mit Breiten-Bedingung → @container |
Breakpoints reagieren auf die Vorschau-Breite, nicht aufs Editor-Fenster |
html / body / :root → :host |
Diese Selektoren treffen im Shadow Root nichts |
Relative url() → absolut (baseUrl) |
Inline-Styles lösen relativ sonst gegen das Admin-Dokument auf |
@import wird eingebettet (fetchCss) |
Konstruierte Sheets werten @import im Browser nicht aus |
Regeln dazu: Nur komplette Queries werden konvertiert (Teil-Umschreibung verboten).
device-width und not bleiben @media.
Unparsebares CSS lädt unverändert plus console.warn mit Zeile und Spalte.
Abschalten je Block, Feld oder Collection:
meta: pagebuilder: cssTransform: falseLookup in der Vorschau
Abschnitt betitelt „Lookup in der Vorschau“Die Vorschau hydriert Fremdschlüssel selbst (hydratePagebuilderRow in lookup-hydration.ts).
Das Muster kennst du aus Lesen und suchen: IDs plus _lookup-Objekt.
- IDs aus
foreignKey/foreignMedia-Feldern sammeln (z. B.blocks.heroImage.image:medialib). - Einträge per
getEntriesByIdsladen (gecacht pronamespace:collection:Feld:id). - Ergebnis unter
row._lookup.<feld>einbetten — der Registry-Block liest daraus. - Datei-URLs per
normalizeLookupUrlauflösen (relativ → Projekt-Basis).
function getLookup(row, key) { return row?._lookup?.[key] || null}
function getMediaSource(row, key = "mediaRef") { const lookup = getLookup(row, key) return lookup?.file?.src || lookup?.url || null}Ohne Lookup steht im Feld nur die ID.
Ohne Leserecht oder ohne active:true am Ziel bleibt _lookup leer — gleiche Regel wie in der REST-Abfrage.
Bildgrenze: MedialibImage ist die einzige Bild-Grenze im Starter (file.src oder Data-URI).
Details: Dateien und Bilder, Medien.
Screenshot- und Thumbnail-Pipeline
Abschnitt betitelt „Screenshot- und Thumbnail-Pipeline“Der Listen-image-Eintrag (meta.preview.image: _pagebuilderThumbnail) kommt aus gerenderten Screenshots.
Kette:
- Regel lesen:
meta.pagebuilder.screenshotincontent.yml(field: blocks,fileField: _pagebuilderThumbnail). - Registry laden und Block im
PagebuilderScreenshotHostrendern (versteckt, pro Regel). - Schriften und Bilder abwarten (Timeout je 4 s, Render-Timeout 12 s).
- Per
modern-screenshot(domToCanvas) aufnehmen. - Pfad bauen:
buildVersionedPagebuilderScreenshotPath→<feld>.v-<hash>.<ext>(webp/jpg). - Ins
file-Feld schreiben (applyFileField).
meta: pagebuilder: screenshot: - field: blocks fileField: _pagebuilderThumbnailSeed für Mock-Daten: scripts/seed-mock-pagebuilder-screenshots.js (Playwright, Thumbnails für die Mock-Registry).
Eigener Block
Abschnitt betitelt „Eigener Block“- Typ in
api/collections/content.ymlanlegen:choices-Eintrag unterblocks.typeplus Felder mitdependsOn-Eval auf$parent.type. - Svelte-Komponente in
frontend/src/blocks/anlegen (z. B.QuoteBlock.sveltemit$props(), SSR-sicher mit Window-Guards). caseinBlockRenderer.svelteergänzen (block.type === "quote"→<QuoteBlock {block} />).- Vorschau-Block in der Registry-Datei ergänzen (gleicher Schlüssel,
defineBlock-Vertrag, eigenes CSS). - Seite speichern — Vorschau und Screenshot laufen automatisch über die neue Registry.
- name: quote type: string meta: dependsOn: eval: $parent.type == 'quote'{:else if block.type === "quote"} <QuoteBlock {block} />Design-Regeln aus dem Starter (api/config.yml, meta.agent.designGuide): Svelte-5-Runes ($props, nie block destrukturieren), Tailwind v4 kanonisch, i18n per $_, Bilder nur über MedialibImage.
Admin-Keys und Verhalten
Abschnitt betitelt „Admin-Keys und Verhalten“| Key | Wirkung | Quelle |
|---|---|---|
viewForm / viewBoth / viewPreview |
Modus-Umschalter im Overlay: Nur Formular, Formular und Vorschau, Nur Vorschau | PagebuilderOverlay.svelte:293,310,326, de.json:598-600 |
closeTitle |
Schließen-Knopf mit ESC-Hinweis („Schließen (ESC)“) | PagebuilderOverlay.svelte:348, PagebuilderOverviewOverlay.svelte:725, de.json:596 |
viewportDesktop / viewportTablet / viewportPhone |
Breiten-Presets 1280 / 768 / 375 in der Viewport-Steuerung | PagebuilderViewportControl.svelte:23-25, de.json:605-607 |
Drill-Down-Modi overlay / inline |
overlay (Standard): Fullscreen-Overlay mit Layer-Stack; inline: direkte Blockliste im Formular bei meta.drillDown: false |
PagebuilderOverviewOverlay.svelte:4-8,34,61,83, PagebuilderWidget.svelte:205,238 |
blueprintMoreFields |
„+{count} weitere” — eingeklappte Restfelder auf der Blueprint-Karte | BlueprintCard.svelte:125, de.json:611 |
blueprintChildren |
Kind-Zähler („1 Kind” / „n Kinder”) | BlueprintCard.svelte:101, de.json:610 |
blueprintNoData |
Leertext „Keine Daten” bei fehlenden Block-Daten | BlueprintCard.svelte:132, de.json:612 |
| ESC / Blur / Drilldown | Escape schließt nur den obersten Layer (isTopLayer), vorher activeElement.blur(); Drilldown-Navigation übernimmt Blur selbst |
PagebuilderOverlay.svelte:190-198,244, PagebuilderOverviewOverlay.svelte:378-382 |
Der Block-Editor (Fullscreen-Split mit Formular und Live-Vorschau), die Viewport-Steuerung (Desktop/Tablet/Mobil-Presets plus Slider) sowie Übersichts-Overlay und Blueprint-Fallback (Schema-Karte bei fehlendem Registry-Typ) sind Admin-Oberfläche — Bedienung siehe Eintrags-Editor. Aus Entwickler-Sicht zählen die Ursachen dahinter:
| Symptom | Ursache | Lösung |
|---|---|---|
| Blueprint-Karte statt Vorschau | Registry-Pfad falsch oder Typ fehlt in der Map | blockRegistry.file prüfen, Schlüssel = type-Wert |
| CSS „blutet” in den Editor | cssTransform: false oder Parse-Fehler (Warnung in der Konsole) |
Warnung mit Zeile/Spalte lesen, CSS fixen |
| Bild leer in der Vorschau | Lookup-ID ohne Ziel oder Ziel ohne active:true |
Medialib-Eintrag prüfen, anonyme Abfrage gegenlesen |
| Altes Thumbnail | Screenshot-Regel fehlt oder fileField falsch |
meta.pagebuilder.screenshot in content.yml prüfen |
| Felder erscheinen nicht | dependsOn-Eval auf $parent.type fehlt |
Eval wie oben setzen, Typ-Schreibweise exakt |
Verifiziert am 2b091d8 2026-10-17 (tibi-admin-nova: de.json admin.pagebuilder.*), am e3f92eb 2026-10-17 (tibi-svelte-starter: content.yml, BlockRenderer, config.yml designGuide); Registry-Ladeordnung und Asset-Specifier verifiziert am 2b091d8 2026-10-17 (load-registry.ts:14-94, project-asset-specifier.ts:17-50)