Niederlassungs-Footer für Vacancy-Detailseiten
Status: accepted Follow-up zu ADR-0014 customer-lens-reviewer: GO-WITH-CONCERN (2026-06-10)62975-23008, die pro Niederlassung statt pro Node gelten. Niederlassung = der required, Single-Cardinality-Taxonomie-Term field_office (Vokabular vacancy_office) — eine saubere 1:1-Beziehung Node → Office-Term. Alle redaktionellen Sections (1, 2, 3, 5) werden als schlichte Felder am vacancy_office-Term abgebildet — ohne neue Module. Section 2 („Arbeiten in …“) ist ein Repeater aus bis zu sechs festen Bild-Text-Card-Feld-Sets (nur befüllte werden gerendert); Section 3 (Adresse + Ansprechpartner:in) und die Sections 1 (Imagevideo) und 5 (kununu-Award + Karriere-Broschüre) sind flache Per-Office-Felder. Das Term-Edit-Formular wird mit field_group (bereits aktiv) in die Section-Gruppen gegliedert. Section 4 („Diese Positionen könnten Dich auch interessieren“) ist dynamisch — ein contextual Views-Block über andere offene Vacancies derselben Recruiting-Kategorie (keine Felder, kein Modul). Gerendert wird nicht über ein Canvas-Content-Template (Canvas ist node-only, verifiziert), sondern durch Erweiterung des bestehenden handkomponierten node--vacancy--full.html.twig — mit explizit gesetztem Cache-Tag für den Office-Term. Das ist der in ADR-0014 ausdrücklich aufgeschobene Out-of-Scope-Block. Bewusst verworfen wurden paragraphs (für Section 2) und block_content (als globale Quelle für 1/5): die Vermeidung zweier neuer Modul-Abhängigkeiten wiegt schwerer als die Repeater-Ergonomie bzw. ein zentraler Global-Default.
Kontext
ADR-0014 fixierte die Topologie der Vacancy-Detailseite (node.vacancy.full) und nannte „zusätzliche Sections“ explizit als Out-of-Scope für den MVP. Dieser aufgeschobene Block kommt jetzt in-Scope: Figma-Frame 62975-23008 zeigt unterhalb der bestehenden Sections (zuletzt Benefits / Gehaltsspanne) fünf weitere Sections, die laut Anforderung pro Niederlassung gepflegt werden — alle Stellenausschreibungen einer Niederlassung sollen denselben Footer-Inhalt ausgeben.
Verifizierte Ausgangslage im Repo:
- Niederlassung = Taxonomie-Term.
field_officeist eineentity_referenceauf das Vokabularvacancy_office, required, Cardinality 1. Daraus folgt eine saubere 1:1-Beziehung Node → Office-Term. Der Term trägt heute nurname(aus Personio gespiegelt) undfield_image. - Die Full-View ist Twig, nicht Canvas.
web/themes/custom/byte_theme/templates/node/node--vacancy--full.html.twigkomponiert die Sections handgeführt aus SDC-Includes. Canvas läuft für Vacancy nur in den View-Modescardundfeatured. - Canvas ist node-only.
web/modules/contrib/canvas/src/Controller/ApiConfigControllers.php:163setzt$supported_entity_type_ids = ['node'];ContentTemplateHooks.php:90trägt einen expliziten@todo, die Node-Beschränkung später aufzuheben. Eincanvas.content_template.taxonomy_term.*lässt sich also gar nicht anlegen — Term-Inhalte sind nicht canvas-fähig. - Personio liefert für das Office nur den Namen. Die XML-Quelle (
migrate_plus.migration.solcom_vacancy.yml,selector: office) trägt keine Straße/PLZ/Geo. Der node-level Freitextfield_vacancy_location_info(Personio-Abschnitt „Standort“) ist pro Stelle und unstrukturiert. Die strukturierte Niederlassungs-Adresse aus Section 3 ist damit nicht aus Personio beziehbar — sie muss redaktionell sein. - Module:
media+views+field_groupaktiv;paragraphsundblock_contentnicht. Es existiert kein etabliertes globales Settings-/Config-Singleton-Muster.views.view.vacancies_listingexistiert als Basis und filtert bereits auffield_recruiting_category. - Vacancies sind aus dem Editorial-Workflow ausgeschlossen (ADR-0026) — sie sind migrationsgeführt. Der Office-Term ist davon nicht betroffen: er ist redaktionell kuratierbar (Term-Labels werden bereits kuratiert,
field_imageist bereits redaktionell ergänzt).
Section-Inventar & Natur-Klassifikation (Figma-Scan 2026-06-10)
Der entscheidende Befund des Scans: die fünf Sections haben drei verschiedene Naturen. Eine naive „5 identische Feld-Gruppen am Term“-Lösung wäre falsch (Section 4 ist gar kein Redaktionsinhalt) und redundant (Sections 1/5 müssten pro Office dupliziert werden).
| # | Section (Figma) | Inhalt | Natur |
|---|---|---|---|
| 1 | Über SOLCOM | Heading + Imagevideo | 🌐 global, überschreibbar |
| 2 | Arbeiten in <Ort> | Heading + 6 Bild-Text-Cards (Repeater) | 📍 pro Niederlassung, redaktionell |
| 3 | Niederlassung + Ansprechpartner:in | Adresse + Google-Maps-Link · Recruiter (Name/Rolle/Tel./Foto) | 📍 pro Niederlassung, redaktionell |
| 4 | Diese Positionen könnten Dich auch interessieren | 4 Job-Cards anderer offener Stellen | ⚙️ dynamisch |
| 5 | SOLCOM stellt sich vor | kununu-Award + Karriere-Broschüre (PDF) | 🌐 global, überschreibbar |
Entscheidung
Alle redaktionellen Sections (1, 2, 3, 5) werden als schlichte Felder am vacancy_office-Term abgebildet — ohne neue Module —, mit field_group gegliedert; Section 4 bleibt ein dynamischer Views-Block; gerendert wird durch Erweiterung des bestehenden Twig-Templates, nicht über Canvas. Konkret:
- Section 2 („Arbeiten in …“) → bis zu sechs feste Card-Feld-Sets am Term. Je Card ein Feld-Paar (Bild als Media-Referenz + Text),
field_office_card_1_*…field_office_card_6_*. Alle optional; das Twig rendert nur die Cards, die Inhalt tragen (5 befüllt → 5 angezeigt). Bewusst keine Paragraphs — die feste Card-Zahl macht starre Feld-Sets ausreichend und spart das Modul (siehe Rejected Alternatives). - Section 3 (Niederlassung + Ansprechpartner:in) → flache Felder am Term. Adresse (Firma/Straße/PLZ/Ort), Google-Maps-URL als
link, Recruiter (Name/Rolle/Telefon/Foto-Media). Die Adresse ist redaktionell, nicht aus Personio (verifiziert: Feed liefert nur den Office-Namen). - Sections 1 (Imagevideo) + 5 (kununu-Award + Karriere-Broschüre) → flache Felder am Term. Section 1: ein Video-Feld (Media bzw. URL). Section 5: Award-Asset + Broschüre (File/Media) + Label. Kein
block_content, kein globaler Default, keine Override-Logik — jede Niederlassung pflegt diese Inhalte selbst (bewusst gewählter Trade-off: keine Module gegen Pflege-Redundanz, falls die Inhalte firmenweit identisch sind). - Section 4 („Diese Positionen …“) → bestehendes Views-Display wiederverwendet. Das bereits existierende Display
vacancies_listing : block_relatederfüllt Section 4 exakt: contextual argfield_recruiting_category_target_id(default_argument_type: taxonomy_tid,node: true) zieht die Kategorie aus der aktuellen Node, ein zweiter argnid(not: true,default_argument_type: node) schließt die aktuelle Stelle aus; Filterstatus=1/type=vacancy,block_hide_empty, Rows imcard-View-Mode. Kein neuer View, kein Redaktions-Feld, kein Modul. Einziger Tune an Figma-Frame:items_per_page3→4 und Heading-Copy auf Du-Ansprache („Diese Positionen könnten Dich auch interessieren“) — display-spezifisch, das Display ist nirgends als Layout-Block platziert. Eingebettet viaviews_embed_view('vacancies_listing', 'block_related')(Default-Args lösen aus dem Route-Kontext auf). - Formular-Gliederung via
field_group. Das Term-Edit-Formular vonvacancy_officewird mitfield_group(bereits aktiv) in benannte, einklappbare Gruppen je Section zerlegt — das hält das feldreiche Formular (Sections 1+2+3+5) übersichtlich. - Rendering: Erweiterung von
node--vacancy--full.html.twig. Die fünf Sections werden im bestehenden handkomponierten Twig ergänzt; der Views-Block (Section 4) sitzt an seiner festen Position zwischen den Term-Sections. Alle benötigten SDCs existieren bereits (verifiziert 2026-06-10): Section 1 →video; Section 2 →card/card-frame/card-feature; Section 3 →bubble-card-frame(Recruiter) +heading/text/button(Adresse + Maps-Link); Section 4 →cardvia Views; Section 5 →badge+download-brochure-card. Kein Greenfield-SDC-Bau. Inline-Render-Mechanik:twig_tweakist nicht aktiv — der Views-Block wird über Core-views_embed_view()eingebettet. - Cache-Korrektheit explizit. Weil per-Hand auf Term-Felder zugegriffen wird (statt Entity-Render mit automatischem Bubbling), wird das Cache-Tag des Office-Terms in
byte_theme_preprocess_node()explizit als Cacheable-Dependency gesetzt. Sonst invalidiert das Bearbeiten von „Stuttgart“ die betroffenen Vacancy-Seiten nicht — was der ganze Zweck von „geteilt pro Niederlassung“ ist. (Ein zweiter Block-Cache-Tag entfällt, da es keinen Globals-Block mehr gibt.) - Keine neuen Module.
paragraphsundblock_contentwerden bewusst nicht eingeführt;media,viewsundfield_group(alle aktiv) genügen.
Rejected Alternatives
Canvas-Content-Template direkt auf dem vacancy_office-Term
Technisch unmöglich. Wäre die eleganteste Variante (Term visuell im Canvas-Builder editieren, kein neuer Node, kein Linking). Aber Canvas-Content-Templates sind hart node-only (ApiConfigControllers.php:163, expliziter @todo in ContentTemplateHooks.php:90); ein canvas.content_template.taxonomy_term.vacancy_office.full lässt sich nicht erstellen. Erst relevant, wenn der Upstream-@todo aufgelöst wird.
Dedizierter „Niederlassung“-Node mit Canvas-Full, referenziert vom Term
Rejected wegen Editor-Friktion. Ein neuer Node-Typ mit Canvas-Full-Template (bewährtes Node-Pattern) plus Entity-Reference vom Office-Term gäbe maximalen visuellen Komfort und freie Komposition. Kostet aber: der Redakteur müsste zwei Entities pro Office pflegen und synchron halten (Node anlegen und am Term verknüpfen), mehr bewegliche Teile, Drift-Risiko (ADR-0039), und einen Canvas-Node in ein Twig-Template einzubetten ist Mehraufwand. Personio spiegelt zudem den Term, nicht einen Node — der Term ist der natürliche 1:1-Anker.
Paragraphs-Feld für Section 2 (6 Bild-Text-Cards)
Erwogen, bewusst verworfen. Ein office_card-Paragraph-Typ (Bild + Text, multi-value) gäbe die ergonomischste Repeater-UX: natives Hinzufügen/Sortieren, garantierte Bild-Text-Kopplung, beliebige Anzahl. Aber: die Card-Zahl ist im Design fix (max. 6), und paragraphs ist heute nicht installiert. Bei fester Obergrenze leisten sechs optionale Feld-Paare am Term dasselbe Ergebnis (nur befüllte Cards rendern) ohne eine neue Modul-Abhängigkeit. Der einzige Verlust — freies Hinzufügen über 6 hinaus — wird nicht gebraucht. Entscheidung des Users: Felder statt Modul.
Zwei parallele Multi-Value-Felder (Bild-Liste + Text-Liste) für Section 2
Rejected wegen fragiler Editor-UX. Modulfrei wäre auch ein field_office_card_images (Media, multi-value) + field_office_card_texts (Text, multi-value), im Twig nach Delta gepaart. Der Redakteur pflegt dann aber zwei getrennte Listen, deren Positionen manuell ausgerichtet bleiben müssen — verrutscht eine, verrutscht die Bild-Text-Zuordnung. Feste Feld-Paare pro Card vermeiden diese Fehlerquelle bei gleicher Modul-Freiheit.
Globale Quelle (block_content oder geschützter Default-Term) für Sections 1+5
Erwogen, bewusst verworfen zugunsten reiner Term-Felder. Ein block_content-Typ „Vacancy Footer Globals“ (eine Instanz, firmenweit) oder ein geschützter „Global“-Default-Term gäbe ein zentrales „einmal pflegen“ für Imagevideo, Award und Broschüre — mit optionalem Per-Office-Override. block_content bringt aber einen eigenen Revisions-/Permission-Stack mit (für eine 2–3-Feld-Section schwer), und ein Default-Term ist nur eine ungeschützte fachliche Konvention mit inverser Pflege-Logik. Beide Varianten kosten mehr Mechanik (Modul bzw. Schutz-Hook, Fallback-Coalesce, Override-Flags) als reine Per-Office-Felder. Entscheidung des Users: keine Module, jede Niederlassung pflegt 1+5 selbst. Der Preis (Pflege-Redundanz, falls firmenweit identisch) ist in den Consequences vermerkt.
Monolithischer Term-Render {{ node.field_office.entity|view('vacancy_footer') }}
Rejected, weil Section 4 dazwischenliegt. Ein einzelner Term-View-Mode-Render würde sauber cachen, kann aber den dynamischen Views-Block (Section 4) nicht aufnehmen, der zwischen Term-Sections 3 und 5 sitzt. Man müsste in zwei Teil-Renders um den Block herum splitten — unübersichtlicher als das bestehende handkomponierte Section-Muster konsequent fortzuführen.
Consequences
Positive
- Minimale Editor-Friktion für den Kernfall. Alle redaktionellen Sections werden an genau einer bereits existierenden Entität gepflegt (dem Office-Term), ohne jegliches Verknüpfen —
field_officeist required und Cardinality 1. Eine Änderung an „München“ wirkt automatisch auf alle Münchner Stellen. - Null neue Module. Kein
paragraphs, keinblock_content— das gesamte Modell ruht aufmedia,viewsundfield_group(alle aktiv). Geringste Wartungs- und Drift-Oberfläche. - Nur zwei Pflege-/Render-Quellen. Redaktioneller Inhalt kommt ausschließlich vom Office-Term; Section 4 ist nullpflegeaufwand (Drupal zieht verwandte Stellen automatisch via contextual Views-Block).
- Kein neues Render-Paradigma. Das bestehende handkomponierte Twig-Muster wird fortgeführt; Canvas (node-only) wird gar nicht erst angetastet.
Negative / Concerns
C-1 — Pflege-Redundanz für Sections 1+5 (bewusst gewählt)
Ohne globale Quelle pflegt jede Niederlassung Imagevideo, kununu-Award und Karriere-Broschüre selbst. Sind diese firmenweit identisch, ist das Doppelarbeit, und ein Broschüren-Wechsel muss in jeder Niederlassung nachgezogen werden. Bewusst akzeptierter Trade-off (User-Entscheidung: keine Module). Rückfallpfad, falls die Redundanz stört: nachträglich eine globale Quelle (block_content oder Config-Singleton) + Per-Office-Override ergänzen — additiv, ohne das Feld-Modell zu brechen.
C-2 — Feldreiches Term-Formular
Sections 1+2+3+5 als flache Felder ergeben grob 20+ Felder am vacancy_office-Term (allein Section 2: bis zu 6 Card-Paare). Mitigation: zwingende field_group-Gliederung in einklappbare Section-Gruppen; klare Feld-Labels.
C-3 — Manuelles Cache-Tag-Bubbling
Per-Hand-Feldzugriff im Twig bubbelt Cache-Tags nicht automatisch. Mitigation: Office-Term-Cache-Tag zwingend in byte_theme_preprocess_node(); ein Render-/Kernel-Test prüft die Invalidierung gegen. (Nur eine Quelle, da kein Globals-Block.)
C-4 — Starre Card-Obergrenze (Section 2)
Feste Feld-Sets deckeln Section 2 auf 6 Cards. Braucht eine Niederlassung mehr, ist ein Schema-Eingriff (weitere Feld-Sets) nötig — nicht bloß ein Klick wie bei Paragraphs. Akzeptiert, weil das Design die Zahl fixiert; bei realem Bedarf an freier Anzahl ist der dokumentierte Pfad der Wechsel auf Paragraphs (Rejected Alternative).
Neutral / Folgeeffekte
- ADR-0014 bleibt gültig — diese ADR realisiert dessen ausdrücklich aufgeschobenen Out-of-Scope („zusätzliche Sections“) und ändert die bestehende Topologie nicht.
- ADR-0026 (Vacancy-Editorial-Ausschluss) bleibt unberührt: der redaktionelle Kanal ist der Office-Term, nicht der Vacancy-Node.
- Adresse Section 3 ist redaktionell (verifiziert: Personio liefert nur den Office-Namen) — nicht migrationsbefüllt; Initialpflege pro Office nötig.
- SDC-Inventar verifiziert (2026-06-10): alle fünf Sections mappen auf bestehende byte_theme-SDCs (
video,card/card-frame/card-feature,bubble-card-frame,badge,download-brochure-card) — kein Greenfield-Bau.twig_tweaknicht aktiv → Section 4 viaviews_embed_view(). - Offen für die Implementation-Phase: a11y der 6-Card-Grid + Recruiter-Block (WCAG-AA), Cache-Context-Verhalten des contextual Views-Blocks (keine anonyme Cache-Varianz brechen), Term-Edit-Permission der Redakteurs-Rolle, Feinabgleich Spacing/Varianten der SDCs gegen Frame
62975-23008.