Ver código fonte

Code-audit: verhuur/catering-tab kapt 17 Amsterdamse zaken af, hartje weg

Twee bevindingen uit een browserloze audit op een verse export, allebei
nagemeten tegen productie:

A. Tab 6 "Verhuur, catering" op horecagelegenhedenOverzichtCurrent is als
   enige niet meegemigreerd naar de gefilterde route. Hij bouwt zijn lijst uit
   getJsonField($).toList().take(25): harde limiet van 25 zonder paginering
   (Amsterdam heeft er 42 -> 17 onbereikbaar), geen zoekveld, crasht bij een
   netwerkfout omdat jsonBody dan null is, en haalt de data twee keer op
   (alleVerhuurCatering wordt gevuld maar nooit gelezen). De take(25) zit in
   het Generate Dynamic Children-paneel, dat op deze pagina aantoonbaar niets
   wegschrijft - dezelfde bekende blokkade, nu met gemeten gebruikersimpact.

B. Het favorietenhartje op de horeca-overzichtskaart bestaat niet meer; het
   sneuvelde in 7094b43 toen Copy3 productie werd. favorieteHorecaNids leeft
   nog maar op de detailpagina, en Favorieten tab 3 heeft nul hartjes. P1-7
   beschreef dit als werkend en is gecorrigeerd.

Nagemeten en schoon: alle 16 launchURL-guards, EstablishmentInfo geeft 1 rij
per zaak (60/60), en P1-45 is weg (categorie is in 65 records over 6 displays
altijd een lijst).

CLAUDE.md: gedrag van getJsonField vastgelegd (1 match -> waarde, meer -> lijst)
plus de twee auditrecepten.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 2 dias atrás
pai
commit
1dc4eeed8e
2 arquivos alterados com 114 adições e 1 exclusões
  1. 34 0
      CLAUDE.md
  2. 80 1
      TASKS.md

+ 34 - 0
CLAUDE.md

@@ -1975,6 +1975,40 @@ method`** — dat is het snelste bewijs. Reken er bij elk nieuw
 lijst-achtig veld op dat je de vorm eerst even met `curl` controleert
 vóór je erop bouwt.
 
+**`getJsonField` geeft bij ÉÉN match de waarde zelf en bij MEERDERE een lijst —
+daar hangt half het project op, en het verklaart de meeste "soms een string,
+soms een lijst"-verrassingen.** De implementatie staat in
+`flutter_flow_util.dart:349`: 0 matches → `null`, 1 match → `field.first.value`,
+>1 match → `List`. De derde parameter (`isForList`, in de export zichtbaar als
+een losse `true`) forceert altijd een lijst. Praktische gevolgen:
+- `$[:].veld` op een respons met **één** record levert gewoon een String op —
+  daarom werken de zes `launchURL`-knoppen op `HorecagelegenheidCurrent` met
+  `$[:].website`. Zodra die view ooit twee rijen per node teruggeeft (het
+  multi-value-veld-patroon) wordt het stil een lijst en opent de knop
+  `[https://...]`. Meet dat dus vóór je zo'n pad vertrouwt.
+- `getJsonField(x, pad).toList()` **zonder** die `true` crasht met
+  `NoSuchMethodError` op alles wat geen Iterable is — óók op `null`, en
+  `ApiManager` levert bij elke exception een respons met `jsonBody == null`.
+  Een lijstwidget die zo gebouwd is, valt dus om bij een netwerkfout; een die
+  uit een `_model`-lijst leest niet.
+- Handige audit op een verse export (vangt beide vormen):
+  `grep -rn "getJsonField" lib/ --include=*.dart -A3 | grep -B3 "\.toList()"`,
+  en filter er de aanroepen met een `true`-argument uit.
+
+**Twee goedkope audits die in dit project echte bugs opleverden — herhaal ze na
+een grote builder-ronde.** Beide draaien browserloos op een verse export:
+1. **Component-parameter gedeclareerd maar nooit gelezen.** Per
+   `*_widget.dart`: haal de `this.x` uit de constructor en check
+   `widget!.x`. Levert het P1-44-patroon op (elke instantie geeft iets mee, het
+   component negeert het) én verraadt verwijderde functionaliteit — zo kwam
+   2026-09-13 aan het licht dat het favorietenhartje van de horeca-
+   overzichtskaart verdwenen was: de parameter `nid` bleef achter als enige
+   spoor.
+2. **`launchURL` achter een `valueOrDefault`-guard** (het P1-15-patroon).
+   Vergelijk per aanroep de guard-expressie met het argument: staat er in de
+   *guard* een `valueOrDefault`, dan is hij altijd waar. Stand 2026-09-13: alle
+   16 aanroepen in levende code zijn schoon.
+
 **Breekpunten en het huispatroon voor responsieve waarden.** De
 constanten staan in `lib/flutter_flow/flutter_flow_util.dart`:
 `kBreakpointSmall` 479, `kBreakpointMedium` 767, `kBreakpointLarge` 991

+ 80 - 1
TASKS.md

@@ -1,5 +1,76 @@
 ## ▶ Nu aan de beurt (stand 2026-09-13)
 
+### 🐞 Nieuw gevonden 2026-09-13 (Claude, code-audit op verse export)
+
+Twee echte bevindingen uit een browserloze audit; allebei nagemeten tegen
+productie, geen van beide eerder opgeschreven.
+
+**A · Tab 6 "Verhuur, catering" is niet meegemigreerd — en kapt content af.
+17 van de 42 Amsterdamse zaken zijn onbereikbaar.**
+Op `horecagelegenhedenOverzichtCurrent` bouwen vijf tabs hun lijst via
+`functions.filterHorecagelegenheden(_model.alleX.toList(), ...)`. De zesde
+(Verhuur, catering) doet het nog op de oude manier: een eigen Backend Query
+plus `getJsonField(jsonBody, r'$').toList().take(25)`. Gevolgen, op volgorde
+van ernst:
+1. **Harde limiet van 25 zonder paginering.** Gemeten met `curl` op
+   `horcat=17968`: Amsterdam (`townid=28695`) heeft **42** verhuur/catering-
+   zaken, de tab toont er **25**. Arnhem heeft er 3, dus daar valt het niet op.
+2. **Geen zoekveld** op deze tab — dit is het bekende "5 van de 6"-restpunt
+   uit P2-6, nu exact gelokaliseerd.
+3. **Crasht bij een netwerkfout.** `getJsonField(..., r'$')` geeft `null`
+   terug als `jsonBody` null is (wat `ApiManager` bij elke exception doet), en
+   `.toList()` daarop gooit `NoSuchMethodError`. De andere vijf tabs lezen een
+   `_model`-lijst en hebben dat probleem niet.
+4. **De data wordt twee keer opgehaald.** De On-Tap-keten vult netjes
+   `_model.alleVerhuurCatering` (regel 483), maar die variabele wordt
+   **nergens gelezen** — de tab negeert 'm en haalt alles nog een keer op.
+
+⚠️ **Waarom dit blijft liggen, en waarom Claude er niet bij kan:** de
+`.take(25)` staat in het **Generate Dynamic Children**-paneel, en dat is
+precies het paneel dat op déze pagina structureel niets meer wegschrijft (zie
+`CLAUDE.md`: zes pogingen, `Max Items` van 25 naar leeg en naar 1000, elke keer
+byte-identiek terug in de export; `Duplicate Page` erft de blokkade). Dat is
+vrijwel zeker ook de reden dat deze tab destijds niet is meegegaan. **Dit is
+dus geen "even naklikken" — het is dezelfde blokkade, nu met een gemeten
+gevolg: er is content die een gebruiker niet kan zien.**
+
+**B · Het favorietenhartje op de horeca-OVERZICHTSkaart is verdwenen.**
+`favorieteHorecaNids` komt in de hele levende export nog maar op één plek voor:
+de detailpagina (`horecagelegenheid_current_widget.dart:382`). Het component
+`HorecagelegenheidoverzichtKaart` heeft geen hartje meer — `git log -S` wijst
+naar commit `7094b43` (taak 18, 2026-09-11), waarin Copy3 de productiepagina
+werd; de hartje-code staat in die diff als verwijderd. De parameter `nid` wordt
+nog wél aan de kaart doorgegeven maar nergens meer gelezen — het overblijfsel
+dat dit verraadde.
+- **Gevolg 1:** favoriet maken kan alleen nog via de detailpagina.
+- **Gevolg 2:** op **Favorieten tab 3** staan nul hartjes (`Icons.favorite`
+  komt 0x voor in `favorieten_widget.dart`), dus je kunt daar niet ontvolgen —
+  je moet eerst doorklikken naar de zaak.
+- P1-7 beschrijft dit hartje nog als "volledig werkend"; die tekst is dus
+  achterhaald. **Besluit nodig:** terugbouwen op de kaart, of bewust laten en
+  P1-7 bijstellen? Het bouwen zelf is een ConditionalBuilder met
+  `List Contains Item` op `widget!.nid` — het recept staat in `CLAUDE.md` en de
+  werkende versie staat nog in `lib/kanweg/kanweghorecagelegenheid_current_copy/`
+  om van af te kijken.
+
+**Wat de audit NIET vond (nagemeten, zodat niemand het nog eens doet):**
+- **Het P1-15-patroon is projectbreed schoon.** Alle 16 `launchURL`-aanroepen
+  in levende code hangen achter een guard op de **rauwe** waarde, niet op een
+  `valueOrDefault`. Geen enkele knop kan op een placeholder-URL tikken.
+- **`EstablishmentInfo` geeft precies één rij per zaak** — 60 Amsterdamse zaken
+  getest, 60x één rij. De zes `$[:].veld`-knoppen op de detailpagina leunen op
+  die aanname (bij meerdere rijen geeft `getJsonField` een lijst en wordt de URL
+  `[https://...]`), maar hij houdt stand. Wel iets om te herinneren als er ooit
+  een multi-value veld aan die view wordt toegevoegd — dat is precies hoe taak
+  30 elders ontstond.
+- **P1-45 is echt weg.** Alle zeven `flutterflowmobiel1`-displays opnieuw
+  gemeten (landelijk + Amsterdam): waar data is, is `categorie` **altijd** een
+  lijst — 65 records over 6 displays, geen enkele komma-string meer.
+  `services_4` blijft op 0 items en is dus niet te toetsen.
+- `SliderUitgaanComponentSmallCurrent` negeert zijn eigen `townid`/`displayid`
+  (het P1-44-patroon), maar het component heeft **nul gebruikers** — geen
+  impact, alleen niet inzetten zonder dat eerst te repareren.
+
 ### 🥇 ALLEEN JIJ KUNT DIT — Drupal/views, Claude komt er niet bij
 
 **Taak 25 · granularity op `flutterflowmobiel1` services_1 — 2 min.**
@@ -2247,7 +2318,15 @@ lokale App-State-lijst bijwerkte.
 
 ---
 
-**P1-7 · Eigenaar: Claude.** "Favorieten pagina maken"
+**P1-7 · Eigenaar: Claude.**
+
+⚠️ **Correctie 2026-09-13: het hartje op de overzichtskaart dat hieronder als
+"volledig werkend" staat, bestaat niet meer** — het is gesneuveld toen Copy3 de
+productiepagina werd (commit `7094b43`, taak 18). Zie bevinding B bovenaan dit
+bestand voor het bewijs en de keuze die er ligt. De rest van P1-7 hieronder
+klopt nog wel.
+
+"Favorieten pagina maken"
 (Bob's naam voor deze taak, 2026-08-13 — was P0-4, samengevoegd met het
 oude P1-7 favorieten/profielscherm-werk + P2-8 gemeente-favoriet). Bob
 bouwt de Drupal-kant (views/endpoints) zelf, apart; Claude/deze sessie