Bladeren bron

Taak 29+39 af: HTML-entiteiten weg uit flutterflow_events, flutterflowmobiel1 (7 displays) en establishments

Gemeten op productie: 1981 records, 0 entiteiten, 318 waarden met een echte &
(tegencontrole). Oorzaken waren dubbele escaping via de rss_category-formatter
en twee velden (adres, horecagelegenheid) die in geen enkele $fields_to_clean
stonden. Inzichten + de views-performancemeting naar CLAUDE.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 16 uur geleden
bovenliggende
commit
45ec617be2
2 gewijzigde bestanden met toevoegingen van 100 en 67 verwijderingen
  1. 67 0
      CLAUDE.md
  2. 33 67
      TASKS.md

+ 67 - 0
CLAUDE.md

@@ -2071,6 +2071,27 @@ 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.
 
+**`FlexibleSpaceBar.background` begint ACHTER de statusbalk — een knop
+bovenin dat vlak is voor ~60% niet aantikbaar, en `adb shell input tap` op
+y < ~63 px (420 dpi) gaat naar Android, niet naar de app.** Bevestigd
+2026-09-14: drie tikken op de hamburger deden niets, een tik op y=90 opende het
+menu meteen. Alleen de *toolbar* van een AppBar krijgt statusbalk-padding; een
+achtergrondwidget niet. Tik bij geautomatiseerde tests dus altijd in de
+onderste helft van een headerknop, en zie `TASKS.md` P1-49 voor de layout-fix.
+
+**De export schrijft `android/` terug naar de OUDE gradle-syntax
+(`minSdkVersion 23`, `task clean(type: Delete)`), terwijl Flutter-tooling die
+bij een `flutter run` migreert naar `flutter.minSdkVersion` +
+`tasks.register(...)`.** Beide werken; de gecommitte staat is de gemigreerde.
+Na een export dus `git checkout -- android/` vóór je staget, anders wisselt
+elke commit de twee varianten af (bevestigd 2026-09-14).
+
+**De `datum` in de views-JSON is taalafhankelijk: op `/nl/` Nederlands
+(`donderdag 10 dec, 20:30`), op `/en/` Engels — en zonder jaartal.** Een
+meetscript moet beide weekdag-/maandvormen kennen en het jaar uit de weekdag
+afleiden (zie `parse()` in de sessie van 2026-09-14); een Engels-only parser
+geeft stil 0 toekomstige events.
+
 **De gedeelde header zit in een `FlexibleSpaceBar.background` met een VASTE
 hoogte — wrap de knoppen-Row daar nooit in een Column.** Bevestigd 2026-09-13 op
 `HeaderButtonsComponent` (staat op 11 pagina's). `Align > Row` krijgt van die
@@ -3169,6 +3190,52 @@ formatter in de view-export; `taxonomy_term_reference_plain` levert een
 string bij single-value (`plaats`) en een lijst bij multi-value
 (`bezorgtin`). Alleen een echt record met data geeft uitsluitsel.
 
+**HTML-entiteiten in een views-JSON lopen allemaal via `_custom_clean_html()`
+in `custom.module`, en die moet HERHAALD decoden.** Afgerond 2026-09-14 over
+drie views (1981 records, 0 entiteiten). Vier dingen die je moet weten:
+- De functie is recursief over arrays en behandelt daaronder alleen strings.
+  Schoonmaken gebeurt **per display-blok** via een `$fields_to_clean`-lijst; een
+  veld dat daar niet in staat wordt nooit behandeld. Dat was de hele oorzaak van
+  vieze `adres`- en `horecagelegenheid`-waarden: die stonden in geen enkele lijst.
+- **De `rss_category`-formatter levert DUBBEL geëscapede waarden.** Het veld
+  `categorie` in `flutterflow_events` (`['term_node_tid_1']['type'] =
+  'rss_category'`) komt binnen als `R&amp;amp;B`; één `html_entity_decode` laat
+  dan `R&amp;B` staan. Daarom decodeert de functie in een lus tot de waarde niet
+  meer verandert (max 3 rondes). Het zusterveld `plaats` gebruikt dezelfde
+  `term_node_tid`-handler zónder die formatter en was altijd al schoon — dat is
+  je controlepaar als je twijfelt of je met dubbele escaping te maken hebt.
+- ⚠️ **Zet het `$fields_to_clean`-blok BINNEN `foreach ($result as &$item)`.**
+  Staat het erbuiten, dan poetst het alleen het láátste record — zonder enige
+  foutmelding, want `$item` is na de loop nog steeds een referentie naar het
+  laatste element. Gebeurde 2026-09-14 in twee blokken tegelijk. `unset($item);`
+  na elke loop maakt zo'n fout meteen zichtbaar.
+- **Meet altijd mét tegencontrole.** Tel niet alleen entiteiten
+  (`&[#a-zA-Z0-9]{2,8};`) maar óók waarden met een kale `&`. Nul entiteiten kan
+  namelijk net zo goed betekenen dat `strip_tags` alles heeft weggevreten; pas
+  "0 entiteiten én N waarden met een echte `&`" is bewijs dat er gedecodeerd is.
+
+**devbob is bruikbaar als testomgeving voor `flutterflow_events`, maar NIET voor
+`flutterflowmobiel1`.** Gemeten 2026-09-14: `flutterflow_events` geeft daar 1183
+events met 106 `&`-waarden (inclusief zowel `Hiphop/Rap/R&B` als
+`Ruiterskwartier 61 & 63`) — ruim genoeg om een clean-fix hard te testen vóór je
+naar productie pusht. `flutterflowmobiel1` filtert op `field_date >= -2 hours` en
+devbob's content is oud: 3 records op s1/s2/s5 en **0** op de andere vier, zonder
+één `&`. Elke curl daar meldt "schoon" ongeacht je fix. Wil je dat deel toch
+vooraf testen: zet op devbob de datum van één event met een `&` in de titel/zaak
+naar volgende week.
+
+**Views is niet de performance-bottleneck — gemeten 2026-09-14, voordat je een
+custom-module-endpoint bouwt om Views te vervangen.** 3x per endpoint met
+cache-buster op productie: een **custom** Services-resource die twaalf
+provincienamen teruggeeft (`plaatsen.json?limit_levels=1`) kost **~1,0 s**;
+`flutterflowmobiel1` services_1 met 25 items kost **~1,2 s**; establishments met
+100 items 1,3-1,8 s; `flutterflow_events` op één nid **0,38 s**. Views kost dus
+~0,2 s van die 1,2 s — de overige ~0,95 s is Drupal-bootstrap + de
+Services-module, en die betaal je in een custom module net zo hard. Dat laatste
+cijfer (dezelfde view, klein resultaat, 0,38 s) bevestigt het. Wil je echt naar
+beneden: caching (deze GET's zijn user-onafhankelijk, zie het hartje-besluit) en
+minder calls per scherm, niet Views vervangen.
+
 **Custom Code-editor: FlutterFlow genereert de functiesignature zelf —
 plak alléén de body.** Bevestigd 2026-09-04: een compleet uitgeschreven
 functie (`String lijstAlsTekst(dynamic items) { ... }`) in het codeveld

+ 33 - 67
TASKS.md

@@ -1,5 +1,10 @@
 ## ▶ Nu aan de beurt (stand 2026-09-14)
 
+**⚠️ Claude — bezig (sessie 2026-09-14/15): verse export naar `lib/` +
+commit, code-audits, contentmeting productie, restpunt 5 (logo-thumbnail
+`BoxFit`) in de builder. Niet parallel aan `lib/` of de aanmaakpagina's
+werken zonder navraag.**
+
 *Afgerond en daarom verwijderd op 13/14 sep: verbindingsmelding in de header,
 offline-crash op de sliders (bevinding C), P2-15 volledig (evenement,
 stadsactiviteit, tijdzone, media), de `.take(25)`-limiet op alle zes
@@ -24,6 +29,27 @@ Emulator-5556 heeft nu de profile-APK van die export + per-app-locale `nl-NL`.*
 > zit erachter. Stel eerst je 3-5 taken voor, wacht op mijn akkoord,
 > verifieer elke wijziging met een verse export en noem het taak-ID.
 
+### 🆕 P1-49 · Hamburger/terugknop zit voor 60% onder de statusbalk · Eigenaar: Claude (na akkoord Bob)
+
+Gevonden 2026-09-14 (Claude, schone start op de telefoon-emulator, 1080×1920
+@420 dpi): de oranje hamburger staat op y 0–105 px, de statusbalk beslaat
+0–63 px. Een tik in dat bovenste deel gaat naar Android (statusbalk), niet naar
+de app — pas een tik op y ≥ 65 opent het menu. **Op een toestel met notch
+(statusbalk 40–48 dp) blijft er vrijwel niets aantikbaar over.** Zelfde
+oorzaak in het menu: het logo in `drawerComponent` staat op 15 px van de
+bovenrand, de kloktijd loopt er dwars doorheen.
+- **Oorzaak:** `HeaderButtonsComponent` zit in `FlexibleSpaceBar.background`,
+  en dat vlak begint op y=0 (achter de statusbalk); alleen de toolbar zelf
+  krijgt van Flutter statusbalk-padding. Geldt op alle 16 pagina's met de
+  component. Home heeft bovendien een afwijkende AppBar (`PreferredSize(50)`,
+  `primaryText`-donker) i.p.v. de standaard 80 px/Info.
+- **Voorstel (één wijziging, alle pagina's tegelijk):** op de root-`Align` van
+  `HeaderButtonsComponent` een **Padding top 28** zetten (24 dp statusbalk +
+  marge); in `drawerComponent` de Padding boven het logo van 15 naar **40**.
+  Op notch-toestellen is 28 nog krap — het nettere alternatief is een custom
+  widget met `SafeArea`, maar dat is meer werk voor hetzelfde resultaat.
+- Verifiëren: `adb shell input tap 275 55` moet daarna het menu openen.
+
 ### 🆕 Favorieten — restpunt voor Bob (stand 2026-09-14 avond)
 
 *Chat-taken 1, 2 en 3 zijn afgerond en geverifieerd met een verse export:
@@ -132,10 +158,13 @@ meer. Niet opnieuw agenderen.
 - Zie ook de nieuwe `CLAUDE.md`-notities over de Set-Variable-dialoog en over
   het verouderde rechterpaneel.
 
-**5 · Media-flow, twee kleinigheden** (uit de live test): de bronkeuze-sheet
-zegt "Choose Source / Gallery / Camera" in het Engels — FlutterFlow-standaard,
-hoort bij de vertaalronde P1-17. En de logo-thumbnail toont een vierkante
-uitsnede van het brede logo.
+**5 · Media-flow — logo-thumbnail ✅ afgerond 2026-09-14 (Claude, builder +
+verse export):** de logo-`Image` op `uitgaansevenementAanmaken` én
+`stadsactiviteitAanmaken` staat nu op **Box Fit: Contain** i.p.v. Cover
+(`fit: BoxFit.contain` op regel 2557 resp. 3457; de foto-thumbnails blijven
+bewust `cover`). Rest alleen nog: de bronkeuze-sheet zegt "Choose Source /
+Gallery / Camera" in het Engels — FlutterFlow-standaard, hoort bij de
+vertaalronde P1-17.
 
 ### 📌 Nagemeten, niet opnieuw onderzoeken
 
@@ -248,46 +277,6 @@ op deze display 0 resultaten, wat suggereert dat bezorgers nauwelijks in de zes
 tabcategorieën zitten — losse pagina ligt dus voor de hand. De pagina heeft nog
 géén `display_id`-parameter; die moet er eerst op.
 
-**Taak 29 · HTML-entiteiten — ✅ `title` opgelost, 2 velden nog open
-(2026-09-14).** Bob heeft `title` door de HTML-clean gehaald en naar productie
-gepusht; geverifieerd op nid 214466 (`Onno Innemee & Ytwer Bosma`) en breed over
-299 events: **0 titels met entiteiten**. Twee velden zijn nog niet meegenomen —
-dezelfde behandeling volstaat:
-- **`adres`** — 26 van de 299 gecontroleerde events, bv. nid 222545
-  `'Ruiterskwartier 61 &amp; 63'`
-- **`categorie`** — 6 van de 299, allemaal `['Hiphop/Rap/R&amp;B']`
-  (nid 222687, 222692, 222697, 222704, 222731, 222741)
-
-Verificatie (moet `GEEN - schoon` geven over 3 pagina's):
-```
-curl -s -u bob:serhii "https://uitgaanskrant.com/en/flutterdrup/views/flutterflow_events.json?display_id=services_1&page=0&limit=100&_cb=$RANDOM" | python3 -c "
-import sys,json,re,collections
-c=collections.Counter()
-for r in json.load(sys.stdin):
-    for k in ('title','adres','categorie'):
-        v=r.get(k); v=' '.join(map(str,v)) if isinstance(v,list) else v
-        if isinstance(v,str) and re.search(r'&[#a-zA-Z0-9]{2,8};',v): c[k]+=1
-print(dict(c) or 'GEEN - schoon')"
-```
-
-_Oorspronkelijke analyse:_
-
-**Taak 29 · HTML-entiteiten op de evenement-detailpagina.** Bevestigd op nid
-214466 (13 sep): `flutterflow_events` geeft `Onno Innemee &amp; Ytwer Bosma`,
-`flutterflowmobiel1` op dezelfde node `Onno Innemee & Ytwer Bosma`. Eén
-database, twee views — dus viewconfig, geen data. Titels handmatig editen is
-zinloos: 837 stuks, en na de volgende import weer terug.
-**Body hoeft niet mee:** die gaat via `EvenementV2HTMLComponent` door een echte
-HTML-renderer en wordt vanzelf gedecodeerd; er is geen dubbele escaping (0
-gevallen `&amp;amp;`). Alleen de platte-tekstvelden, gemeten over 9625 events:
-`title` **837** (`&amp;` 695, `&#039;` 179, `&quot;` 54), `adres` **89**,
-`categorie` 22, `organisator` 1.
-Eerste verdachte: **`_custom_clean_html` in `custom.module`** — kijk of daar op
-viewnaam gefilterd wordt; staat `flutterflowmobiel1` er wel in en
-`flutterflow_events` niet, dan is dat de fix en is het één regel. Anders beide
-views exporteren (`drush @prod php-eval "print views_get_view('X')->export();"`)
-en aan Claude geven om te diffen.
-
 **Taak 31 · dubbele rijen — ⚠️ OORZAAK GEVONDEN, en het is NIET Distinct
 (2026-09-14).** Bob zette `distinct = TRUE` op de Master (staat in de export) en
 het veranderde niets. Dat kan ook niet, en de meting wijst de echte oorzaak aan:
@@ -480,29 +469,6 @@ al begonnen (10:00 en 14:00, gemeten om 20:50) en worden dus terecht door het
 zijn stadsactiviteiten, die worden handmatig aangemaakt en niet geïmporteerd.
 Geen bug.
 
-### Taak 29 · HTML-entiteiten — nu precies af te bakenen (2026-09-12)
-
-**Het raakt alléén de evenement-detailpagina.** Gisteren kon ik dat niet
-scheiden omdat de lijstviews maar 4 items hadden; met de nieuwe vulling is het
-hard te maken op **dezelfde node** (nid `214466`):
-
-| view | wat de app ermee doet | uitkomst |
-|---|---|---|
-| `flutterflowmobiel1` | Home-tabs, sliders, P-pagina's | `Onno Innemee & Ytwer Bosma` ✅ |
-| `flutterflow_events` | **evenement-detailpagina** (`EvenementCall`) | `Onno Innemee &amp; Ytwer Bosma` ❌ |
-
-Voor de gebruiker: hij ziet de titel netjes in de lijst, tikt erop, en op de
-detailpagina staat `&amp;`. Geraakt zijn `title` (124 events), `body` (234),
-`adres` (23) en `categorie` (8) — 321 van de 2007 events.
-
-**Fix:** `flutterflow_events` doet iets anders met zijn tekstvelden dan
-`flutterflowmobiel1`. Beide views draaien op dezelfde nodes, dus vergelijk de
-**veldinstellingen** van `title`/`body`/`adres` tussen die twee en neem over wat
-`flutterflowmobiel1` doet. Geen app-fix proberen: dan moet je op elke plek
-apart decoderen.
-(Zelfde mechanisme als bij je devbob-bezorgdisplay — daar was het ook een
-veldinstelling per display, niet iets in de data.)
-
 ### Taak 30 · Node vermenigvuldigt zich — blijkt niet zichtbaar voor de gebruiker
 
 Nid **`214439`** ("Brocante Markt Klein Frankrijk") komt 14 keer identiek terug