Architecture Decision Record · 0036

project_offer.field_matching_score als Phase-2-Stub — Field jetzt anlegen, Befüllung später

Entscheidung. Das integer-Feld field_matching_score (0–100 %) wird jetzt am node/project_offer als Phase-2-Stub angelegt — mit required: false, default_value: 0, ohne befüllenden Sync. Trigger ist Figma-Frame 985-50024 („Matching Rate" als Card-Chrome-Pill): das SDC card-bubble-profile Listing-Variant bindet den Score visuell als feste Card-Komponente. Ein erzwungenes „kein Score → kein Pill" hätte das SDC-Schema bidirektional optional gemacht und Canvas-Validator-Drift erzeugt. Befüllung folgt in einer eigenen Phase-2-Story (Matching-Service-Integration über SF-Connect, Trigger noch offen).

Kontext

Figma-Frame 985-50024 (Projektpartnerportal-Listing, Customer-Lens-Verdict B-Hybrid, ADR-0031) zeigt jede Project-Offer-Card mit einer „Matching Rate N%"-Pill als oberster Card-Komponente — visueller Anker, der den Mehrwert der Plattform-Brücke gegenüber Freelancer-Plattformen kommuniziert. Die Pill ist im Design fest, nicht conditional.

Die Backing-Source für den Score (Matching-Service zwischen Freelancer-Profil und SF-Project-Offer) ist Phase 2: kein Service definiert, kein Trigger, kein Datenpfad. MVP-Cut-off 08.07.2026 (siehe Auto-Memory project_go_live_deadline) erlaubt das nicht im aktuellen Sprint.

Die Frontend-Implementation in dieser PR (card-bubble-profile.twig Listing-Variant) braucht den Prop matching_score, weil die Pill Teil des SDC-Chrome ist (vor dem Content-Slot, nicht im Slot). Drei Wege standen zur Wahl:

  1. Field jetzt als Stub anlegen, default 0, kein Sync — Score wird überall „Matching Rate 0%" mit gedimmtem Text (text-white/70) rendern.
  2. Kein Field, SDC-Prop nullable machen, Pill nur conditional rendern.
  3. Hardcoded mock-Score im Canvas-Template (z. B. "matching_score": 85 statisch).

Entscheidung

Option 1: Field jetzt als Stub anlegen. field.storage.node.field_matching_score.yml (integer, unsigned: false — erlaubt theoretisch −100..100, defensiv für künftige delta-Scores) plus field.field.node.project_offer.field_matching_score.yml (label „Matching Score", min 0 / max 100 / suffix „%", default 0, required: false) gehen in config/sync/. Kein Sync-Code, keine Default-Value-Callback-Logik. Das Canvas-Template node.project_offer.card bindet die Pill statisch auf matching_score: 0 bis Phase 2 die echte Field-Quelle anbindet.

Die SDC-Prop bleibt required (integer 0–100). Das Twig (card-bubble-profile.twig) rendert den Score-Text in matching_score == 0 ? 'text-white/70' : 'text-white' — d. h. der „leere" Zustand ist nicht „unsichtbar", sondern gedimmt aber lesbar. Die Pill ist also nicht conditional, sondern hat zwei visuelle Zustände: Score-vorhanden (volle Sättigung) und Stub-Zustand (gedimmt). Im Live-Bild bei jeder Card erscheint „Matching Rate 0%" gedimmt, bis Phase 2 echte Scores liefert.

Rejected alternatives

Consequences