53be3ef898
Replace the fragile "store raw bodytext only" workaround with the durable
fix: when a custom Fluid partial renders an RTE field (enableRichtext=true,
e.g. tx_heroitem.bodytext), the backend RTE->DB transform wraps it in
<p>…</p> on every editor save, and a bare {field} / f:format.nl2br render
escapes it -> literal <p> on the page. Render RTE fields with f:format.html.
Add the general rule (check TCA enableRichtext; sweep your own/overridden
partials for nl2br/bare RTE output) and note core templates already do this.
Bump to v1.2.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
19 KiB
19 KiB
name, description, metadata
| name | description | metadata | ||||
|---|---|---|---|---|---|---|
| typo3-t3bootstrap-live-design-parity | Match a TYPO3 v14 t3bootstrap-based build (local DDEV) to a reference LIVE site page by page — the frontend/design phase after a v11->v14 upgrade. Use when making an upgraded TYPO3 site look like its old/live counterpart, when doing per-page visual parity against a reference URL, when content that exists in the DB isn't rendering after a v11->v14 migration, or when customizing a t3bootstrap sitepackage's templates/SCSS/settings. Covers: the Playwright compare loop (computed style + CSS rule), the settings-vs-SCSS decision, recurring structural fixes (stranded colPos, missing templateLayout partials, template partialRootPaths precedence/shadowing, on-demand component CSS, unregistered CTypes), a full visual-parity checklist (layout/width, typography, colors, links/buttons, equal-sizing, icons, borders, footer, header/nav, hero carousel), and cache-safe techniques (full-bleed bands, image-border restyle, responsive crop variants, client-side randomisation, FAL-via-DataHandler). Assumes the site already boots on v14 (see the v11->v14 upgrade skill). |
|
TYPO3 v14 t3bootstrap — live-design parity
Make a v14 t3bootstrap-based DDEV build look like a reference live site (usually the old v11 site being replaced), page by page. There are two layers, in order:
- Structural — the right content actually renders (migration artifacts often hide it).
- Visual parity — typography/colors/spacing/components match the reference.
Compare with Playwright ([headed, in the user's visible window]); read computed values, don't eyeball. Brand tokens (colors, fonts, page IDs, label text) are site-specific — fill the placeholders below from the site you're matching; keep a per-project log of what you changed.
How the v14 t3bootstrap theme works (resolve these first)
- TypoScript via site sets, not
sys_template. ThePAGEobject comes from the theme set, declared underdependencies:inconfig/sites/<SITE_ID>/config.yaml. (Missing → "No page configured for type=0".) flux/vhs are gone in v14; content ist3bs_*container CTypes. - Sitepackage
<TEMPLATE_PACKAGE>(extension key<TEMPLATE_EXTKEY>), usually a fork oft3bootstrap/template, often installed from adev-<branch>git checkout invendor/. Content-element overrides live inEXT:<TEMPLATE_EXTKEY>/Resources/Private/Extensions/ fluid_styled_content/{Templates,Partials,Layouts}; per-CType TypoScript auto-imports fromConfiguration/TypoScript/Lib/ContentElement/*.typoscript. - ⚠ Workflow: edits in a
dev-<branch>vendor checkout must be committed & pushed to the template repo before anycomposer update <TEMPLATE_PACKAGE>(else lost). Code changes (templates/SCSS/TypoScript/tsconfig/JS in the sitepackage) deploy viacomposer update. Content changes (tt_content, settings.yaml, DB) are project-local and travel via a DB dump, NOT the repo.
Per-page compare loop
- Open live
https://<LIVE_HOST>/<path>and DDEVhttps://<DDEV_HOST>/<path>in Playwright. Cache-bust DDEV with?cb=N(browser caches);ddev exec vendor/bin/typo3 cache:flushafter config/SCSS/template changes (a hard flush — alsorm -rf var/cache/*— for template/TS path or on-demand-CSS changes). After editing a Fluid partial/template,cache:flushalone can still serve the old render → also clearvar/cache/code/fluid_template/*+var/cache/data/pages/*.- ⚠
?cb=Nbusts the PAGE HTML but NOT the compiled-CSS asset URL. Playwright cachesmain.cssacross navigations, sogetComputedStylemay read stale styles after a recompile (a rule "missing" from the CSSOM though it's on disk). Verify against server truth: in-pagefetch(cssUrl,{cache:'no-store'})and grep the rule, orgrepthe compiled file on disk; don't trust the cached DOM. Same for server-rendered HTML —fetch(url,{cache:'no-store'})to confirm a template change actually took, rather than the cached page.
- ⚠
- With
browser_evaluate, capture the real element's computed style AND its explicit CSS rule (getComputedStyle + walkdocument.styleSheetsfor the selector). Don't assume inheritance. - Diff vs DDEV → decide setting vs SCSS (below) → apply → flush → re-screenshot → verify.
Settings vs SCSS decision
- Setting (
config/sites/<SITE_ID>/settings.yaml) for anything mapped in the theme'sConfiguration/TypoScript/Plugin/Setup/tx_wsscss.typoscript:design.color.{primary,secondary, body-color,headings-color,header-bg,page-bg,footer-bg,footer-color,footer-headline-color,…},design.font.{font-family-base,headings-font-family,font-size-base,h1..h6-font-size}, plusheader.logo,footer.logo,meta.favicon. These are injected as SCSS vars, so editing_variables.scssfor colors/fonts has NO effect — settings win. - SCSS (a custom partial
@imported last inbase-layout.scss; sizes in rem) for everything with no setting: utility classes, component tweaks, link/hover colors, icon/logo sizing, equal-sizing. - Webfonts: no setting → add the Google-Fonts URL to
page.includeCSSin the theme'sHTML/Page/head.typoscript; a font-downloader ext localizes it. - Vanilla-JS components: register in
page.includeJSFooter(themehead.typoscript), file underResources/Public/JavaScript/components/.
Layer 1 — Structural fixes (content that won't render). Do these FIRST.
These are v11→v14 migration artifacts, not design; they recur across many pages.
- Stranded
colPos=1(the #1 recurring bug). v11 content sitting atcolPos 1(or 2/3) is not rendered by the v14 backend layout → the page looks half-empty though the content is in the DB. Find it site-wide:SELECT c.pid,p.title,COUNT(*) FROM tt_content c JOIN pages p ON p.uid=c.pid WHERE c.colPos=1 AND c.hidden=0 AND c.deleted=0 AND p.doktype=1 GROUP BY c.pid;(also check colPos NOT IN the known container values 2110xx). Fix:UPDATE tt_content SET colPos=0, sorting=sorting+<OFFSET> WHERE pid=<pid> AND colPos=1 AND deleted=0— offset so moved rows sort AFTER the colPos-0 intro. Add a band class viaelement_classeswhere the live section has a background. - Missing templateLayout partial — "Partial Layouts/List/ not found" (news) / "List/ not
found" (address). A v11 custom
templateLayout(e.g.List,Jobs,Lightbox) has no v14 partial. Fix: port the partial into the sitepackage's news/addresspartialRootPathsdir AND register the layout in page TSconfig (tx_news.templateLayouts { X = X }/tx_address.templateLayouts). Empty-list "no results" message can be suppressed by overriding the list template for that layout. - Template partial PRECEDENCE / shadowing. Fluid searches
partialRootPathshighest-index first. Extensions register at different indices (e.g.t3bootstrap_newsat.13, your sitepackage at.12) → the higher one shadows yours, so your override is dead code (symptom: your template edits don't show; a partial you didn't write renders). Fix: register the sitepackage's paths at a higher index (e.g..20), and base your override on the partial that actually renders (diff it against the reference — don't rewrite from a different base). - On-demand component CSS loads AFTER main.css.
scssComponents:require key="…"(accordion, pagination, carousel, …) injects its stylesheet after your compiledmain.css, so equal-specificity overrides lose. Use higher specificity or!important, or a direct target (e.g. recolour an accordion chevron via::after { background-image: … }, not the CSS var). - Unregistered custom CType → FE fatal (empty-
templateNameInvalidTemplateResourceException). Re-register the CType for v14: TCA override (addRecordType/addTCAcolumns, guard withdefined('TYPO3')),tt_content.<ctype> =< lib.contentElement+templateNameinLib/ContentElement/*.typoscript, and the FSC template/partial. If the old site used the native t3b_core rendering (e.g. itsIcon.htmlicon-tile partial that parsespath;class;identifier), reuse that instead of porting old code. - Content ≠ template. If a page's content differs from the reference, first check whether it's an editor-managed content divergence (the dump predates live edits) rather than a template bug — don't "fix" the template to reproduce editor content.
Layer 2 — Visual-parity checklist (run ALL for each page/section)
- Layout / width: full-bleed section background (viewport width) with content contained (~container
width).
full_width=1wrongly makes the content full-width too → keepfull_width=0and bleed only the bg in SCSS:.<band>{box-shadow:0 0 0 100vw <color>;clip-path:inset(0 -100vw)}. - Typography (rem): font family + base size/line-height; heading sizes; card/teaser titles
(v14 often defaults bigger/bolder than the old design); teaser text size/weight. The theme's base
rule is
h1..h6{font-weight:500}; live is often 400 → override globally in your SCSS (equal specificity, appended later → wins). Page-title spacing: every frame carriest3b-pt-l/t3b-pb-l(~24px) padding, so a page title gets doubled gaps above/below vs live's ce-header-margin model. Scope to.page-main .page-content > .frame-type-header:first-child:margin-topfor the gap ABOVE (breadcrumb→title);padding-bottom:0+& + .frame{padding-top:0}for the gap BELOW. - Colors (settings first): primary/headings/nav/links, body text, section-band bg, footer bg + labels. Read the element's explicit rule (inheritance assumptions bite).
- Links & buttons: underline on/off (match live); base + exact hover colors (links→accent,
buttons→accent bg — read the
:hoverrule). Match the button base style (live.btn-primaryis often an outline look: white bg + brand border/text, not filled). Buttons are frequently square — no border-radius on some brands (global.btn{border-radius:0}). A "read more"/CTA may be a plain link + leading chevron (::before "\203A") on live vs a styled button in v14. - Repeated items (cards/teasers/logos/icon tiles): live usually forces equal height AND width
across the row (
height:100%in an equal-height row; imagesobject-fit:cover+ fixed height; logo boxes equal size). DDEV often renders ragged — measure siblings, don't assume. - background-media hero image (textmedia with
asBackground, usually empty text — e.g. section landing/"Auftraggeber" pages): the image is a CSS background on a grid column sitting on a grey band. Live renders it flush + container-width; DDEV insets it (framet3b-pt-l/pb-lpadding + a.bg-lightgrayfull-bleed). Scope.frame-type-textmedia:has(.bg-column.col-type-media): zero the frame padding, drop the full-bleed and move the grey onto> .container, media colpadding-left:0, inner> div{height:100%; background-size:cover}(+ amin-heightso an empty-text row still shows). - Images / borders: the
imageborderfield renders Bootstrap's padded grey.img-thumbnail; restyle to the brand look (border-color:<c>;border-radius:0;padding:0;background:transparent). Migratedimageborder/border flags may be inverted or on the wrong elements — verify against live and fix in the DB. Divider (hr.ce-div) style, footer top border, header separator, decorative brand SVG shapes (stripes/polygons) — port from the old partial. - Icons: match the live pixel size (icon box + svg in rem); center (
text-align:center+margin-inline:auto); hover → accent. - Structural extras (new-but-not-old): remove v14 extras the old site lacks (empty
.card-footer/news-meta, wrappers, unwanted pagination) — remove from the DOM (<f:if>), notdisplay:none. Note Bootstrap utilities (d-none/d-sm-block) are!important→ out-specify or remove the markup. - Footer: columns via
footer.contentPageId; meta nav vianavigation.footer.enabled+folderPageId; disable credits (footer.creditsEnabled:false+ gate the Copyright partial). The.layout-full .page-footerrule may out-specify a plain.page-footer— match its specificity. Footer link columns may be migrated as RTEframe-type-text(not menu elements) → target the text links, excludingmailto:/tel:, when adding chevrons. - Header / navigation: meta menu =
navigation.meta.enabled+folderPageId; nav alignment/logo =header.logoPosition. Top-level shortcut items may need to be toggle-only (href="#") if the live nav doesn't navigate on them.navigation.main.menuTypeis GLOBAL — a per-item mega/dropdown mix + the mega's grid/width/padding needs SCSS work (smartmenus writes inline styles on open →!important). Uppercase mega headers:text-transform:noneto get title-case. A persistent green/ highlighted background on the current-page item + section header in the mega/dropdowns is--bs-dropdown-link-active-bg— set ittransparent(scope to the mega) to remove the "it stays highlighted" effect while keeping--bs-dropdown-link-hover-bgfor transient hover. - Hero / carousel (
hero-item): the ext injects its ownHero.htmlat a fixedpage.5partialRootPaths index → re-assert your partial at a HIGHER index inpage.typoscript, or your edits don't show..carousel-fade= crossfade vs.slide= slide. The.carousel-indicatorsstrip is a full-width high-z-index bar that steals hover from content beneath — reposition/shrink orpointer-events:none. Contain-vs-full-bleed per design. - Translucent overlays — paint ONE shape: a translucent box + a separate translucent shape beside
it can never be seamless (hairline gap or doubled-alpha line). Merge into one element with one
clip-pathpolygon + onergba(). - i18n:
vendor/bin/typo3 language:update(composer mode ships no packs → English fallback). - Verify: flush + screenshot with
?cb=N; confirm by reading computed values.
Cache-safe & content techniques
- Randomise/shuffle on every reload → client-side JS. The FE page is cached, so a server-side
shuffle (
ORDER BY RAND) freezes one order per cache lifetime. Do it in the browser. To avoid a flash, run an inline<script>right after the markup (before Bootstrap's DOMContentLoaded auto-init) rather than a footer component. Check CSP first (curl -D-for acontent-security-policyheader) — if enforced, inline scripts need a nonce; else prefer an external component. (Same technique for a shuffled team/address grid.) - Responsive per-device image cropping (hero bg): the bg renders via
t3bc:backgroundImage useBootstrapCropVariants=true, which reads a per-breakpoint cropVariant NAME fromsettings.breakpoints(xs/sm→s576,md→s768,lg→s992,xl→s1200,xxl→s1400). To let editors crop per device, add cropVariants with those exact keys (page TSconfigTCEFORM.tt_content.<falField>.config.overrideChildTca.columns.crop.config.cropVariants— page TSconfig avoids TCA load-order issues). Base recommended aspect ratios on the element's measured rendered proportions per breakpoint (measure with Playwright +browser_resize), keep a "Frei" (free) option. - Bulk-create content elements + FAL references via a bootstrapped CLI PHP script (SystemEnviron-
mentBuilder + Bootstrap::init + CommandLineUserAuthentication + DataHandler). Gotcha: creating a
type=fileFAL relation via DataHandler datamap creates thesys_file_referencebut leavesuid_foreign=0(child not wired to parent) and the parent counter 0 → afterprocess_datamap(), read$dh->substNEWwithIDs[...]and directlyUPDATEthe ref'suid_foreign+ set the parent file-count column = 1, thenreferenceindex:update. Import images with$storage->addFile(…, DuplicationBehavior::REPLACE)(or$storage->getFile('/path')to index an already-copied file).- Single-element clone WITHOUT DataHandler (simplest for "add one slide/CE like an existing one"):
clone the sibling
tt_contentrow and itssys_file_referenceviaConnectionPool(unsetuid, repoint the ref'suid_local/uid_foreign) — the copied file-count column stays correct, so it sidesteps theuid_foreign=0gotcha entirely. - RTE field rendered escaped by a custom template → literal
<p>on the page. Symptom: after an editor edits+saves an element (e.g. a hero slide — even just its title), its rich-text field shows literal<p>…</p>in the FE. Cause: the field is RTE (enableRichtext=truein TCA — e.g.tx_heroitem.bodytextint3bootstrap/hero-item), so the backend's RTE→DB transform wraps the value in<p>…</p>on every save, but the custom Fluid partial renders it as ESCAPED plain text (<p>{…bodytext -> f:format.nl2br()}</p>or a bare{…}) → the tags are escaped and shown. FIX = render it as the RTE HTML it is:{record.data.<field> -> f:format.html()}(lib.parseFunc_RTE), and drop the manual<p>/nl2br. This renders editor-saved<p>correctly AND wraps raw (script-imported) text in a paragraph — so no per-slide data cleanup. (A bulk-import script that stores RAW text into an RTE field hides this until the first editor save — don't rely on that; fix the template.) General rule: whenever a custom template renders a field, check the field's TCAenableRichtext— if true, usef:format.html, never bare{field}orf:format.nl2br(both escape). Core/t3b_core templates already do this (e.g. image captiontext_visible, newsteaser), so the risk is only in your own/overridden partials — sweep them fornl2br/bare RTE-field output. - Applying customer copy across DDEV+staging: key the UPDATE on a stable identifier (e.g. the bg
image filename via
tt_content→sys_file_reference→sys_file), since row uids differ per env.
- Single-element clone WITHOUT DataHandler (simplest for "add one slide/CE like an existing one"):
clone the sibling
Deploy the design changes
- Code (templates/SCSS/TS/tsconfig/JS): commit + push the sitepackage branch, then on the target
run
composer update <TEMPLATE_PACKAGE>(NOT plaincomposer install— that keeps the stale locked ref) + hard cache flush so SCSS recompiles. ⚠ ws_scss writes the compiled CSS in a<theme>/subdir (public/typo3temp/assets/css/<theme>/main.css), sorm css/*.css(non-recursive) MISSES it and ships a stalemain.css— removecss/<theme>/main.css(or the wholecss/tree). It recompiles on the first frontend hit (behind basic-auth, that's the user's browser reload). - Content (tt_content/settings/DB): travels via a DB dump or replay the same DB edits; keep them env-agnostic (e.g. key UPDATEs on stable identifiers like file names, since uids differ per env).