Architecture Decision Record · 0031

project_offer Frontend-Decomposition — Layer A (MVP) + Layer B (Post-MVP)

proposed Layered approach aufgelöst — siehe docs/plans/2026-05-14-001-feat-project-offer-sync-bh4sf.html

TL;DR. Die Frontend-Implementierung von Story F-290 (project_offer) wird in zwei Lieferschichten aufgeteilt. Layer A (MVP — bis 08.07.2026) umfasst alle Baseline-Features: View-Mode-Configs, Pathauto, Canvas-Komposition project_offer.full (nach Figma-Frame 1019-78888), Canvas-Card-Template project_offer.card (nach Frame 985-50077), Drupal-View project_offers_listing mit 5 BEF-Filtern, Canvas-Page /projektpartnerportal, Custom-Block PPP-Cross-Sell. Layer B (Post-MVP) enthält Vollparität (Skill-Badges, Geo-Filter, Merkliste, Matching-Rate, Projekt-Alert). Bundle project_offer spiegelt vacancy (Vacancy-Parität als Schablone), baut aber auf den gleichen SDC-Bausteinen auf.

Kontext

Story P2503-108 (F-290) synchronisiert SF-Publications via BH4SF REST-Pull nach Drupal (ADR-0029). Das Backend-Modul solcom_project_offer (Sub-Tasks P2503-128/129) ist bereits committed. Die Frontend-Seite — also die Darstellung der synced Project-Offers für Besucher — wurde im Refinement-Grilling als eigenständige Scope-Schicht identifiziert.

Das Figma liefert zwei entscheidende Frames für diesen Layer:

  • Frame 1019-78888 (Detail-Page): Komposition aus bubble-profile, card-meta-list, meta-row, button, Cross-Sell-Slot und Sticky-CTA-Bar. Entspricht konzeptionell dem Vacancy-Detail-Layout (ADR-0014), aber ohne editorialen Workflow (ADR-0026).
  • Frame 985-50077 (List-Card): prop-driven Card-Variante mit card-meta-list. Ist eine Variante des bestehenden byte_theme/card-SDC (ADR-0009), kein eigenes SDC.

Das Refinement hat die naive Annahme „project_offer = vacancy 1:1 klonen" korrigiert. Die Unterschiede zur Vacancy-Detail-Page (ADR-0014) sind:

  • Kein Featured-Slot (Vacancy hat Hero-Billboard-Slot oben)
  • Kein Related-View im Detail (Vacancy hat „Ähnliche Stellen"-Block)
  • Cross-Sell-Block statt Related-View: PPP-eigener Custom-Block-Plugin
  • Sticky-CTA-Bar in Detail: eigener Canvas-Slot, keine generische Drupal-Block-Region

Gleichzeitig gibt es 9 weiterte Features (Layer B: Geo-Filter, Skill-Badges, Merkliste, …), die technisch komplex sind und für den MVP-Go-Live 08.07.2026 nicht benötigt werden. Sie einzeln in einen Riesen-PR zu packen würde die Review-Last erheblich erhöhen und den MVP-Termin gefährden.

Die zentrale Frage war: Aufteilen in Layer A + B, oder lieber alles auf einmal ausliefern, um spätere Nacharbeit zu vermeiden?

Entscheidung

Zweistufige Frontend-Lieferung. Layer A ist MVP-Pflicht (Sub-Tasks P2503-137 bis P2503-145 im laufenden PR), Layer B wird als separate Story nach MVP-Go-Live geplant (Sub-Tasks P2503-147 bis P2503-152).

Layer A — MVP (in Branch feature/P2503-108)

Sub-TaskInhaltFigma-Ankerpunkt
P2503-137View-Mode-Configs default + card für project_offerFrame 1019-78888 + 985-50077
P2503-140Canvas-Komposition project_offer.full (Detail)Frame 1019-78888
P2503-141Canvas-Card-Template project_offer.card (List-Card)Frame 985-50077
P2503-142Pathauto-Pattern unter /projektpartnerportal/
P2503-143Drupal-View project_offers_listing + 5 BEF-FilterFrame 985-50077 (List-Kontext)
P2503-144Canvas-Page /projektpartnerportal + Custom-Block PPP-Cross-SellFrame 985-50077 (Page-Komposition)
P2503-145MAPPING-Update + ADR-0031 (diese Datei)

Layer B — Post-MVP (separate Story nach 08.07.2026)

Sub-TaskInhalt
P2503-147Skill-Badges (Taxonomy-basierte Pill-Darstellung)
P2503-148Remote-Anteil-Slider (Range-Widget im Filter)
P2503-149Suchradius-Filter (Geo-Proximity via BEF + Geocoder)
P2503-150Merkliste (anonyme Session-Favoriten, User-Feature)
P2503-151Matching-Rate V1 (faked Score-Display)
P2503-152Projekt-Alert (Email-Subscription-Feature)

Vacancy-Parität als Implementierungs-Schablone

Bundle project_offer spiegelt vacancy — Pathauto-Patterns (/projektpartnerportal/{title} analog /stellenangebote/{title}), gleiche SDC-Bausteine (bubble-profile, card-meta-list, button, meta-row). Das Vacancy-Canvas-Template (ADR-0014) dient als Blaupause für project_offer.full, aber die Slot-Konfiguration weicht ab (kein Featured-Hero-Slot, Cross-Sell statt Related-View). Die Canvas-Card-Template-Variante folgt dem Muster von vacancy.card (prop-driven, keine eigenen SDC-Bausteine, nur Konfiguration via Canvas-API).

Rejected alternatives

1 — Alles auf einmal (Layer A + Layer B) im MVP-PR

Scope würde 13 Sub-Tasks (P2503-137 bis P2503-152) plus den Pre-Build-Gate P2503-146 (FCSB-Live-Describe) in einem einzigen PR bündeln. Konsequenz:

  • Geo-Filter (P2503-149) hat unbekannte Drupal-Geocoder-Abhängigkeiten, die zu Blocker-Risiko bis Go-Live werden.
  • Merkliste (P2503-150) und Projekt-Alert (P2503-152) sind User-Features mit eigenem Auth-Scope — beide hängen von F-400 (User-Linking Phase 2) ab.
  • Review-Last für den PR steigt linear mit Sub-Task-Anzahl; Scope-Creep-Risiko ist dokumentiert.
  • Rejected: Go-Live 08.07.2026 ist harter Termin. Lieber solider MVP als verzögerter Vollausbau.

2 — Komplett eigenes Bundle ohne Vacancy-Parität

Statt project_offer als Vacancy-Spiegel zu modellieren, könnte ein eigenständiges Bundle mit eigenen Feldnamen, eigenen View-Modes und eigenen SDCs gebaut werden.

  • Vorteil: maximale Entkopplung.
  • Nachteil: Theme-Drift — zwei fast identische SDC-Konfigurationen (vacancy.full und project_offer.full) divergieren über Zeit, Pflege verdoppelt sich.
  • Nachteil: Keine Code-Wiederverwertung der vorhandenen Vacancy-Canvas-Templates und SDC-Patterns, die bereits validated sind (ADR-0014, ADR-0013).
  • Rejected: Vacancy-Parität ist praxiserprobte Schablone, die Drift-Risiko minimal hält.

3 — Reaktive State-Bibliothek (Vue/React) für Filter

Für den project_offers_listing-View könnten die 5 Filter als Vue- oder React-Komponenten implementiert werden, die per API die Ergebnisliste nachladen.

  • Vorteil: flüssigere UX beim Filtern, kein Full-Page-Reload.
  • Nachteil: JS-Bundle für einen einzigen View-Filter, der primär SEO-relevante Ergebnisse ausliefert (Publications sind indexiert). BEF + AJAX-View reicht funktional aus.
  • Nachteil: erhöhte Komplexität beim Decoupled-Setup (API-Endpoint, CORS, Auth für anonyme User) weit over-engineered für den vorliegenden Use Case.
  • Rejected: BEF + AJAX-View ist der etablierte Drupal-Weg für gefilterte Views, kein eigenständiges JS-Framework nötig.

4 — Canvas-Card als eigenes SDC statt Variante von byte_theme/card

Für Frame 985-50077 (List-Card) könnte ein eigenes byte_theme/card-project-offer-SDC angelegt werden.

  • Nachteil: Card-Familie (ADR-0009, ADR-0017) hat klare Zuständigkeiten. Ein fünftes Card-SDC für einen single-bundle Use Case widerspricht dem Pattern.
  • Figma-Analyse hat bestätigt: Frame 985-50077 ist strukturell identisch mit der bestehenden Card-Variante — nur die Prop-Belegung unterscheidet sich (Publication-spezifische Felder).
  • Rejected: Prop-driven Variante via Canvas-Card-Template ist das richtige Pattern (ADR-0009).

Consequences

Sofortige Konsequenzen (Layer A)

  • Zwei neue Canvas-Templates: canvas.content_template.node.project_offer.full (P2503-140) und canvas.content_template.node.project_offer.card (P2503-141) wurden als Config-YAML in config/sync/ angelegt. Figma-Frames 1019-78888 resp. 985-50077 sind die visuellen Ankerpunkte.
  • Pathauto-Pattern: /projektpartnerportal/[node:title] (P2503-142). Neue URL-Struktur ist nach Go-Live kaum noch änderbar — Entscheidung ist bindend.
  • View project_offers_listing: 5 BEF-Filter (Branche, Skills, Remote-Anteil, Ort/PLZ, Vollzeit/Teilzeit). Geo-Proximity-Filter explizit Layer B.
  • Custom-Block-Plugin PPP-Cross-Sell: im Custom-Modul solcom_project_offer als BlockBase-Plugin (P2503-144). Dieser Block ersetzt den Related-View-Slot in Detail-Page und List-Page.
  • Kein Editorial-Workflow (ADR-0026): project_offer-Nodes werden nicht durch Redakteure bearbeitet. Der Drupal-Publish-Status folgt dem SF-Lifecycle (SC_PublicationStatus__c == "Online"). Die Vacancy-Editorial-Ausnahme gilt analog.

Bewusste Restrisiken

  • Layer-B-Schulden: Die 6 Post-MVP-Features sind in Jira als Sub-Tasks erfasst (P2503-147 bis P2503-152), aber noch nicht Bestandteil eines priorisierten Sprints. Risiko: Layer B wird nach Go-Live deprioritisiert und bleibt dauerhaft offen.
  • Geo-Filter-Blocker: P2503-149 (Suchradius-Filter) hat unbekannte Geocoder-Drupal-Abhängigkeiten und könnte bei Aufnahme in Layer B seinerseits einen Pre-Build-Gate brauchen.
  • Vacancy-Parität-Drift: Wenn Vacancy-Canvas-Templates (ADR-0014) sich weiterentwickeln, muss project_offer.full manuell nachgezogen werden. Kein automatischer Sync.

Folge-Stories (nicht in diesem ADR)

  • Layer-B-Sprint-Planung nach Go-Live (Termin offen).
  • User-Linking Phase 2 (F-400) als Voraussetzung für Merkliste (P2503-150) und Projekt-Alert (P2503-152).
  • FCSB-Live-Describe (P2503-127) als Pre-Build-Gate für Layer-B-Features, die auf Field-Drift sensitiv sind.

Amendment 2026-05-14

Domain-Rename + Layer-A/B-Auflösung (2026-05-14). Zwei wesentliche Änderungen gegenüber dem ursprünglichen proposed-Zustand:
  1. Naming-Update: Bundle sf_publicationproject_offer, Modul solcom_sf_publicationsolcom_project_offer, View publications_listingproject_offers_listing. Alle Canvas-Template-IDs (node.sf_publication.*) entsprechend umgestellt. Der Rename ist als atomarer PR committed (Slice 1 des Plans). Begründung: Domain-Begriff von solcom.de/de/projektportal übernommen; „Publication" ist nur noch Implementierungs-Detail des BH4SF-RecordType.
  2. Layer-A/B-Trennung aufgelöst: Der Grilling-Output vom 2026-05-14 hat ergeben, dass beide Layers MVP-Pflicht sind — ein vollständiges Projektpartnerportal (inkl. Skill-Badges, Remote-Anteil, Geo-Filter, Merkliste, Matching-Rate, Projekt-Alert) ist Stakeholder-Erwartung zum Go-Live 08.07.2026. ADR-0031 bleibt als historisches Dokument für die ursprüngliche Refinement-Linie; die operative Planung liegt in docs/plans/2026-05-14-001-feat-project-offer-sync-bh4sf.html (12 Slices, alle AFK).

Customer-Lens

Konsultation ausstehend. Der customer-lens-reviewer wurde für diesen ADR noch nicht konsultiert. Status bleibt proposed. Die Konsultation obliegt dem Orchestrator nach dem Merge des PRs feature/P2503-108. Erst danach kann der ADR auf accepted gesetzt werden.

Voraussichtliche Lens-Anwendung (zur Vorbereitung der Konsultation):

  • Figma-Parity-Lens: Frames 1019-78888 und 985-50077 müssen im Canvas-Output erkennbar wiedergegeben werden. Sticky-CTA-Bar in Detail und Cross-Sell-Block in List sind für die Besucher-Journey relevant.
  • A11y-Lens: Card-Meta-List-SDC und Bubble-Profile-SDC tragen WCAG-2.1-AA-Verantwortung. BEF-Filter-Widgets müssen keyboard-navigierbar sein.
  • Performance-Lens (N/A für Layer A): BEF + AJAX-View wird clientseitig keine signifikante JS-Last hinzufügen. Layer B (Geo-Filter) könnte Maps-Integration erfordern — dann Performance-Lens relevant.