project_offer.field_matching_score als Phase-2-Stub — Field jetzt anlegen, Befüllung später
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:
- Field jetzt als Stub anlegen, default
0, kein Sync — Score wird überall „Matching Rate 0%" mit gedimmtem Text (text-white/70) rendern. - Kein Field, SDC-Prop nullable machen, Pill nur conditional rendern.
- Hardcoded mock-Score im Canvas-Template (z. B.
"matching_score": 85statisch).
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
- Option 2: SDC-Prop nullable, Pill conditional. Verworfen: Das SDC-Schema müsste zwei Wahrheiten kennen (mit/ohne Pill), Canvas-Editor-Surface würde inkonsistent — Authors sehen die Pill je nach View-Mode mal als Chrome, mal nicht. Außerdem verschiebt sich das Card-Spacing je nach Pill-Existenz, was die Listing-Höhe instabil macht. Bei Phase-2-Anbindung müsste das Schema rückwärts upgrade-fähig sein (Prop von optional auf required), was Canvas-Versions-Diff produziert.
- Option 3: Hardcoded Mock-Score im Canvas-Template. Verworfen: erzeugt einen toten Daten-Pfad, der im Phase-2-Rollout aktiv zurückgebaut werden muss. Außerdem suggeriert „Matching Rate 85%" gegenüber Stakeholdern/Demos, dass die Mechanik funktioniert — was nicht stimmt und das Demo-Vertrauen frisst, wenn der Mock auffliegt.
- Field-Stub in einen eigenen PR ausgliedern. Erwogen, verworfen: Die zwei YAML-Dateien sind ein In-One-Field-Stub ohne Code-Pfad, ohne Migration, ohne UI-Form — kein Risiko, der die Trennung lohnt. Der Frontend-PR (Figma-Alignment) braucht das Feld als Prop-Binding, und PR-internes Bundling hält Spec→Field→SDC-Prop→Canvas-Binding in einer Review-Einheit.
Consequences
- Das Field existiert ab Merge in der DB-Schicht (Tabelle
node__field_matching_score), wird aber von keinem Sync befüllt — allenode/project_offertragen den default-Wert0. Manuelle Befüllung über die Edit-Form ist möglich (UI-Widget number_field), wird aber im MVP nicht erwartet. - Phase-2-Story (Matching-Service-Anbindung, noch ohne Jira-Key) wird das Field befüllen, ohne Schema zu ändern: Default-Value-Callback oder Migrate-Process-Pipeline injiziert den Score aus dem Matching-Service. Kein hook_update_N nötig, kein Canvas-Versions-Bump.
- Das Frontend rendert bis dahin „Matching Rate 0%" gedimmt auf jeder Card. Das ist sichtbar, aber kein UX-Defekt: die Marken-Kommunikation („Matching" als Plattform-Versprechen) ist konsistent vorhanden, nur ohne Inhalt — analog zu einem Skeleton-State. Stakeholder werden in der Demo-Note auf den Phase-2-Status hingewiesen.
- Datenschutz: keine PII, keine externe Datenquelle, kein Privacy-Surface. Default-Wert
0ist datenschutz-neutral. - ADR-0032 (Migrate-Sync-Hygiene für
solcom_project_offer) wird nicht berührt — Sync-Mapping schreibt das Field nicht, weil SF kein Match-Score liefert. Wenn Phase 2 das Mapping erweitert, gilt ADR-0032 Regel 3 (Outbound-Field-Binding). - Wird die Phase-2-Befüllung später vom Wechsel des Wertebereichs angefasst (z. B. delta-Score −100..100), bleibt das Field-Storage-Schema (integer,
unsigned: false) bereits dafür vorbereitet. Das ist absichtlich defensiv gewählt; rein technisch reicht 0..100 heute.