From f7ddc1b3ce0a06ee4b3974fa5bf4fe99e9b582b5 Mon Sep 17 00:00:00 2001 From: "bert.hausmans" Date: Sat, 25 Apr 2026 04:52:01 +0200 Subject: [PATCH] docs: close base scope-class extraction follow-up (post-WS-5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reflects the FormFieldChildTableMorphScope extraction landed in the previous commit: - ARCH-FORM-BUILDER.md v1.9 — five locations updated: §6.7 (Relational binding table) — added forward reference sentence after the FormFieldBindingScope escape-hatch line (WS-5a was the first scope; previously had no deferral note because nothing existed yet to defer) §17.4.2 (Relational table form_field_validation_rules) — "deferred to WS-5d per addendum Q3" replaced with marker- subclass forward reference §17.5.3 (Service, scope, cascade — config) — same replacement §17.6.1 (Field options rationale) — "unblocks the deliberate follow-up" replaced with completion-confirmation §17.6.3 (Service / scope / cascade — option) — "deferred to a follow-up work package" replaced with marker-subclass forward reference + Phase A diff verification result Version metadata + changelog updated; v1.8 prose preserved in the Previous-versions block. - ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md — new "Uitvoering — base scope-class extractie (2026-04-25)" section inserted after the WS-5d Uitvoering, documenting the Phase A diff-verification, marker-subclass approach, private→protected YAGNI policy, the inline-FQN → use-statements stylistic refinement, static-analysis impact (Larastan baseline clean, Rector 357 → 355), and net-diff figures. - BACKLOG.md — FORM-BUILDER-MORPH-SCOPE-BASE-CLASS item closed via strikethrough header + "Status: closed 2026-04-25" annotation (matches the TECH-TS-PORTAL-TSC closure convention from earlier this week). - SCHEMA.md — three stale "deferred" claims updated to reflect the completed extraction: header v2.6 changelog mention rewritten to point at the now- landed FormFieldChildTableMorphScope form_field_validation_rules table-section global-scope note replaced with marker-subclass forward reference form_field_options table-section global-scope note same replacement Schema version NOT bumped — no actual schema change. The two other scope mentions (form_field_bindings, form_field_configs) made no deferral claims and remain accurate. Note: the work package's prose listed "§6.7 / §17.4.3 / §17.5.3 / §17.6.3" as deferral-note locations. The actual locations were §17.4.2 (not §17.4.3), §17.5.3, §17.6.1 (not just §17.6.3), and §17.6.3 — §6.7 had no deferral note (WS-5a was the first scope, nothing to defer yet). All five spots updated in line with the work package's intent. WS-5 family fully complete: no open follow-up items remain under the "delete > adapt" discipline of the WS-5 refactor. Tests: 1208 passed (3260 assertions). No code changes in this commit. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md | 62 +++++++++++++++++ dev-docs/ARCH-FORM-BUILDER.md | 69 ++++++++++++------- dev-docs/BACKLOG.md | 16 +++-- dev-docs/SCHEMA.md | 20 +++--- 4 files changed, 130 insertions(+), 37 deletions(-) diff --git a/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md b/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md index daae7166..2bc69538 100644 --- a/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md +++ b/dev-docs/ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md @@ -230,6 +230,68 @@ WS-5d splitst zowel `form_fields.options` als `form_field_library.options` naar **Afronding WS-5d.** 6 commits (commit 0 cleanup + 5 WS-5d core), baseline tests 1158 → 1208 volledig groen na commit 5. Breaking change acceptance: geen bridging compatibility layer — vier portal componenten (`FieldRadio`, `FieldSelect`, `FieldMultiselect`, `FieldCheckboxList`) gemigreerd naar `OptionSpec[]` rich-shape met locale-aware label-resolutie via `providePublicFormLocale` injectie en `resolveOptionLabel(option, locale)` helper in `@form-schema/types/formBuilder`. apps/app blijft onaangeraakt — `RegistrationField*.vue` componenten consumeren een ander legacy domein dat geen WS-5d migratie nodig heeft. **WS-5 familie compleet.** +### Uitvoering — base scope-class extractie (2026-04-25) + +Sluit de WS-5-familie follow-up `FORM-BUILDER-MORPH-SCOPE-BASE-CLASS` +af. Vier concrete polymorfe morph-scope siblings landed across WS-5a/ +b/d (`FormFieldBindingScope`, `FormFieldValidationRuleScope`, +`FormFieldConfigScope`, `FormFieldOptionScope`); de discipline-keuze +"abstract pas na vier concrete kopieën" werd door §17.4.2 / §17.5.3 / +§17.6.1 / §17.6.3 expliciet uitgesteld. + +**Phase A diff-verificatie clean.** De vier concrete scope-files +waren byte-equal in `apply()` en `resolveOrganisationId()` (63 regels +per body, drie pairwise diffs leeg) — geen divergentie, geen +verborgen behavior-variatie tussen siblings. Het empirische +antwoord op "wat varieert daadwerkelijk?": niets. De abstractie is +een pure logica-deduplicatie. + +**Aanpak: identity-preserving abstract base + marker subclasses.** + +- `FormFieldChildTableMorphScope` (nieuw, abstract) bevat de volledige + UNION-over-two-owner-chains logica + de twee morph-alias-constanten + (`OWNER_TYPE_FIELD = 'form_field'`, + `OWNER_TYPE_LIBRARY = 'form_field_library'`) als private constants. +- De vier bestaande sibling-classes worden marker subclasses: + `final class X extends FormFieldChildTableMorphScope {}`. Lege body, + enkel-regel declaratie. +- Klasse-identiteit blijft behouden: alle bestaande + `Model::withoutGlobalScope(FormFieldXScope::class)` aanroepen in + cascade observers, backfill migraties en platform super_admin + paths werken zonder wijziging. Vier test-call-sites in de + `FormFieldXScopeTest` klassen werken eveneens zonder wijziging. + +**Sichtbaarheid: `private` blijft.** De abstract class' helpers +blijven private. Als een toekomstige sibling alternatieve morph- +aliassen of owner-chains nodig heeft, promote dan +`private → protected` op dat moment. YAGNI-discipline: geen +flexibility-by-default. + +**Geen tests gewijzigd.** De vier bestaande scope-tests +(`FormFieldBindingScopeTest`, `FormFieldValidationRuleScopeTest`, +`FormFieldConfigScopeTest`, `FormFieldOptionScopeTest`) verifiëren +publiek gedrag dat byte-equal blijft. Test + assertion tellingen +identiek pre- en post-refactor: 1208 / 3260. + +**Net diff:** 5 files changed, +165 / -377. ≈219 regels duplication- +verlies over de vier sibling-files, ~125 regels toegevoegd in de +nieuwe base class, ~80 regels in de vier marker-subclass declaraties +combined. + +**Static-analysis gates.** Larastan baseline blijft clean (geen +nieuwe errors buiten baseline; abstract base + marker subclasses +zijn type-compleet). Rector dry-run: 357 → 355 suggesties (kleine +reductie door dedupliceerde apply-bodies). + +**Stylistisch: `use` statements i.p.v. inline FQNs.** De vier +originals gebruikten `\App\Models\Organisation` en `\App\Models\Event` +inline in `resolveOrganisationId()`. Bij de extractie zijn die +gemigreerd naar `use App\Models\Organisation;` / `use App\Models\Event;` +boven aan het bestand. Geen functionele wijziging, alleen leesbaarheid. + +**WS-5-familie volledig afgerond.** Geen open follow-up items meer +onder de "delete > adapt" discipline van WS-5. + --- ## Q4 — Sanctum `personal_access_tokens` diff --git a/dev-docs/ARCH-FORM-BUILDER.md b/dev-docs/ARCH-FORM-BUILDER.md index b5123541..b855aa90 100644 --- a/dev-docs/ARCH-FORM-BUILDER.md +++ b/dev-docs/ARCH-FORM-BUILDER.md @@ -1,4 +1,4 @@ -# ARCH — Universal Form Builder (v1.8) +# ARCH — Universal Form Builder (v1.9) > **Source of truth** for Crewli's universal Form Builder architecture. > Any discrepancy with SCHEMA.md is resolved in favour of this document @@ -14,11 +14,19 @@ > pre-WS-5d `options` JSON columns dropped on both `form_fields` and > `form_field_library`; per-option translations live on the option row > itself). **WS-5 family complete.** -> **Version:** 1.8 (new §17.6 "Field options (relational)" for the WS-5d -> split; §17.4 / §17.5 sibling-catalogue prose extended to mention the -> fourth concrete morph-scope; existing Webhooks section renumbered -> from §17.6 to §17.7). +> **Version:** 1.9 (FormFieldChildTableMorphScope abstract base class +> extracted across the four WS-5 siblings; concrete scope classes are +> now marker subclasses preserving identity for existing +> `withoutGlobalScope(SubclassName::class)` call sites; deferral notes +> in §6.7 / §17.4.2 / §17.5.3 / §17.6.1 / §17.6.3 replaced with forward +> references to the base; rationale in +> ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md §"Uitvoering — base +> scope-class extractie"). > **Previous:** +> 1.8 (new §17.6 "Field options (relational)" for the WS-5d +> split; §17.4 / §17.5 sibling-catalogue prose extended to mention the +> fourth concrete morph-scope; existing Webhooks section renumbered +> from §17.6 to §17.7), > 1.7 (§8 restructured into tree-structure, relational-tables, > service-boundary, operator-catalogue, cycle-detection, activity-log > and legacy-migration sub-sections; contract unchanged), @@ -1284,6 +1292,15 @@ explicit override via constructor, then route parameter `organisation` the scope. Escape hatch: `FormFieldBinding::withoutGlobalScope(FormFieldBindingScope::class)`. +Post-WS-5d, `FormFieldBindingScope` is a marker subclass of +`FormFieldChildTableMorphScope`; the shared UNION-over-two-owner-chains +logic lives in the abstract base. See +`app/Models/Scopes/FormFieldChildTableMorphScope.php`. Identity- +preserving extraction landed after the four sibling scopes existed +(rationale + Phase A diff verification in +ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md §"Uitvoering — base +scope-class extractie"). + **Service boundary (`FormFieldBindingService`):** all writes go through the service — no controller fills bindings directly on the model. The service owns: @@ -2465,11 +2482,12 @@ Organisation context resolution mirrors `OrganisationScope`; the escape hatch is `FormFieldValidationRule::withoutGlobalScope(FormFieldValidationRuleScope::class)`. -Base-class extraction between the two scope classes is deliberately -deferred to WS-5d per addendum Q3 — premature abstraction from two -siblings is still premature, and WS-5d's `form_field_options` / -WS-5c's `form_field_conditional_logic` may surface a different shared -shape. +`FormFieldValidationRuleScope` is a marker subclass of +`FormFieldChildTableMorphScope`; the shared logic lives in the +abstract base. See `app/Models/Scopes/FormFieldChildTableMorphScope.php`. +Identity-preserving extraction landed after WS-5d — rationale and +Phase A diff verification in ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md +§"Uitvoering — base scope-class extractie". **Service boundary (`FormFieldValidationRuleService`).** All writes go through the service — no controller writes rules directly on the @@ -2628,11 +2646,13 @@ Mirrors §17.4's validation-rules stack one-for-one: - **Service boundary** (`FormFieldConfigService`) — `configsFor`, `replaceConfigs`, `copyConfigs`, `toJsonShape`, `assertSpecsValid`. Single writer; all controller paths go through it. -- **Multi-tenancy** (`FormFieldConfigScope`) — third near-duplicate of - `FormFieldBindingScope`. The three siblings' base-class extraction is - deferred to WS-5d per addendum Q3 (abstracting from three is still - premature when the fourth sibling is about to land and may clarify - what truly varies). +- **Multi-tenancy** (`FormFieldConfigScope`) — marker subclass of + `FormFieldChildTableMorphScope`; the shared UNION-over-two-owner- + chains logic lives in the abstract base. See + `app/Models/Scopes/FormFieldChildTableMorphScope.php`. Identity- + preserving extraction landed after WS-5d (rationale in + ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md §"Uitvoering — base + scope-class extractie"). - **Cascade** — shared `FormFieldChildTablesCascadeObserver` (renamed from `FormFieldBindingsCascadeObserver` in WS-5b commit 1) covers all three relational tables on owner delete. @@ -2691,8 +2711,9 @@ per-locale translations JSON map. WS-5d follows the WS-5a / WS-5b discipline one-for-one: dedicated service as single writer, UNION-over-two-owner-chains scope, shared cascade observer. Fourth and final WS-5 sibling — landing it -materialises the four concrete morph-scope implementations and -unblocks the deliberate follow-up of base-class extraction. +materialised the four concrete morph-scope implementations. The +follow-up base-class extraction landed in a separate work package +post-WS-5d (`FormFieldChildTableMorphScope`). #### 17.6.2 Table + catalogue @@ -2739,12 +2760,14 @@ it. FormRequests in their `after()` hook to reject malformed specs at the HTTP boundary before any write. -`FormFieldOptionScope` is the fourth concrete UNION-over-two-owner- -chains sibling, near-duplicate of the binding / validation-rules / -configs scopes. Base-class extraction across the four siblings is -deliberately deferred to a follow-up work package now that the four -implementations exist and the "what actually varies" question can be -answered empirically. +`FormFieldOptionScope` is a marker subclass of +`FormFieldChildTableMorphScope`; the shared UNION-over-two-owner- +chains logic lives in the abstract base. See +`app/Models/Scopes/FormFieldChildTableMorphScope.php`. The "what +actually varies" question across the four siblings was answered +empirically (Phase A diff verification clean: nothing varies). See +ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md §"Uitvoering — base +scope-class extractie". `FormFieldChildTablesCascadeObserver` extended to physically delete option rows on owner soft-delete OR force-delete; options are diff --git a/dev-docs/BACKLOG.md b/dev-docs/BACKLOG.md index 0756d2ca..5a8fc46b 100644 --- a/dev-docs/BACKLOG.md +++ b/dev-docs/BACKLOG.md @@ -345,12 +345,18 @@ shifts claimen zonder toegang tot de Organizer app. --- -### FORM-BUILDER-MORPH-SCOPE-BASE-CLASS — Extract base class across the four WS-5 morph-scope siblings +### ~~FORM-BUILDER-MORPH-SCOPE-BASE-CLASS — Extract base class across the four WS-5 morph-scope siblings~~ -**Aanleiding:** Post-WS-5d, four near-duplicate scope classes implement the same UNION-over-two-owner-chains shape: `FormFieldBindingScope` (WS-5a), `FormFieldValidationRuleScope` (WS-5b), `FormFieldConfigScope` (WS-5b), `FormFieldOptionScope` (WS-5d). Each is ~40 lines of boilerplate plus the morph-alias names; the `apply()` body and the `resolveOrganisationId()` helper are byte-identical across all four. Base-class extraction was deferred during each WS-5 commit on the principle of "abstraction from N copies is premature when N+1 lands soon"; with the fourth concrete implementation now in place, the "what actually varies" question can be answered empirically. -**Wat:** Extract `App\Models\Scopes\FormFieldChildTableScope` abstract base. Subclasses declare only the two morph alias strings (`form_field`, `form_field_library`) — actually identical across all four siblings, so even the subclasses become trivial. Keep the four concrete classes as named entry points (the `withoutGlobalScope(FormFieldOptionScope::class)` escape-hatch contract is API surface). Single low-risk refactor PR; full WS-5 test suite must stay green byte-for-byte. -**Prioriteit:** Middel — quality-of-life refactor. No functional change, no schema change, no contract change. Land before any further child-table morph-pattern is introduced. -**Related:** Addendum §Q3 WS-5d Uitvoering paragraph "Vier scope-siblings → base-class extractie nu warranted". +**Status: closed 2026-04-25** — `FormFieldChildTableMorphScope` +abstract base extracted; the four concrete scopes are now marker +subclasses preserving identity. Phase A diff verification confirmed +the four concrete `apply()` + `resolveOrganisationId()` bodies were +byte-equal (zero divergence across three pairwise comparisons). Net +diff: +165 / −377 lines. Tests 1208 → 1208 (3260 assertions, identical). +Larastan baseline clean; Rector dry-run 357 → 355. +See `app/Models/Scopes/FormFieldChildTableMorphScope.php` and +ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md §"Uitvoering — base scope- +class extractie (2026-04-25)". --- diff --git a/dev-docs/SCHEMA.md b/dev-docs/SCHEMA.md index e7a97aea..eb996978 100644 --- a/dev-docs/SCHEMA.md +++ b/dev-docs/SCHEMA.md @@ -16,13 +16,15 @@ > rich-shape `[{value, label, sort_order, translations?}, ...]`; the > parallel pre-WS-5d `translations.{locale}.options[]` arrays stripped > from both source rows and field-snapshot translation bags — option -> translations live on the option row now. WS-5 family complete; base -> morph-scope class extraction across the four siblings -> (`FormFieldBindingScope`, `FormFieldValidationRuleScope`, -> `FormFieldConfigScope`, `FormFieldOptionScope`) deferred to a -> follow-up work package now that all four concrete implementations -> exist. See ARCH-FORM-BUILDER.md §17.6 and -> ARCH-CONSOLIDATION-ADDENDUM-2026-04-24 §Q3 WS-5d Uitvoering. +> translations live on the option row now. WS-5 family complete; the +> follow-up base morph-scope class extraction across the four +> siblings (`FormFieldBindingScope`, `FormFieldValidationRuleScope`, +> `FormFieldConfigScope`, `FormFieldOptionScope`) landed post-WS-5d +> as `FormFieldChildTableMorphScope` (abstract base; the four +> concrete classes are now marker subclasses preserving identity). +> See ARCH-FORM-BUILDER.md §17.6 and +> ARCH-CONSOLIDATION-ADDENDUM-2026-04-24.md §"Uitvoering — base +> scope-class extractie". > - v2.5: WS-5c — `form_fields.conditional_logic` JSON column **dropped**; > replaced by a two-table relational tree: > `form_field_conditional_logic_groups` (AND/OR nodes with optional @@ -2181,7 +2183,7 @@ that aggregates the user's submitted, non-test `form_submissions`. **Relations:** `morphTo` owner (`form_field` or `form_field_library`) **Indexes:** `(rule_type)`, `(owner_type, owner_id)` **Unique constraint:** `UNIQUE(owner_type, owner_id, rule_type)` -**Global scope:** `FormFieldValidationRuleScope` — sibling to `FormFieldBindingScope`, same UNION shape over both owner chains. Escape hatch: `withoutGlobalScope(FormFieldValidationRuleScope::class)`. Base-class extraction deferred to WS-5d per addendum Q3 (premature abstraction from two siblings is still premature). +**Global scope:** `FormFieldValidationRuleScope` — marker subclass of `FormFieldChildTableMorphScope` (post-WS-5d extraction); shared UNION-over-two-owner-chains logic lives in the abstract base. Escape hatch: `withoutGlobalScope(FormFieldValidationRuleScope::class)`. **Soft delete:** no — rules are current state, not audit --- @@ -2237,7 +2239,7 @@ that aggregates the user's submitted, non-test `form_submissions`. **Relations:** `morphTo` owner (`form_field` or `form_field_library`) **Indexes:** `(owner_type, owner_id, sort_order)` as `ffo_owner_sort_idx` **Unique constraint:** `UNIQUE(owner_type, owner_id, value)` as `ffo_owner_value_unique` — seed-bug guard -**Global scope:** `FormFieldOptionScope` — fourth and final sibling in the morph-scope family (alongside `FormFieldBindingScope`, `FormFieldValidationRuleScope`, `FormFieldConfigScope`); same UNION shape. Escape hatch: `withoutGlobalScope(FormFieldOptionScope::class)`. Base-class extraction across the four siblings deferred to a follow-up work package now that all four concrete implementations exist. +**Global scope:** `FormFieldOptionScope` — marker subclass of `FormFieldChildTableMorphScope` (post-WS-5d extraction); shared UNION-over-two-owner-chains logic lives in the abstract base alongside the three sibling marker subclasses (`FormFieldBindingScope`, `FormFieldValidationRuleScope`, `FormFieldConfigScope`). Escape hatch: `withoutGlobalScope(FormFieldOptionScope::class)`. **Soft delete:** no — options are current state, not audit. Submission snapshots carry the historical shape. ---