*Deze lijst is op 2026-09-19 teruggebracht van 6676 naar een paar honderd
regels: alle afgeronde taken en sessieverslagen zijn eruit gehaald. Ze staan in
de git-historie (commit 92225d0 is de laatste versie met alles erin) en de
blijvende inzichten staan in CLAUDE.md. Alles hieronder is open werk.
Staat iets er niet meer in, dan is het klaar of afgewezen — niet opnieuw
agenderen.*
Voor een nieuwe sessie: pak een taak uit "Claude — open" hieronder, zet er
meteen — bezig achter in dit bestand (een echte Edit, niet pas bij het
afsluiten; andere sessies delen deze working directory), en verifieer elke
builder-wijziging met een verse export. Taken onder "Bob — open" niet zelf
oppakken.
| # | Taak | Kort |
|---|---|---|
| 88B | Pagina "Mijn aanmeldingen" | 🔒 Eigenaar: Claude — bezig (2026-09-19, sessie 'profiel-hub'). Onderdeel van taak 104 hieronder |
| 104 | MijnProfiel wordt accounthub |
🔒 Eigenaar: Claude — bezig (2026-09-19). Drawer-knop, tab 4 leegtrekken, aanmeldingen-teaser. Eigen blok hieronder |
| 65 | Release-build doorloop | ✅ Uitgelogde deel af 2026-09-19 (0 crashes; 65a, 65b en 65d-kleur opgelost; 65c + 65e open). Rest = ingelogd + Xiaomi → Bob |
| Categoriedropdown horeca-overzicht | ✅ AF 2026-09-19 (Claude, export-geverifieerd): opties+labels per tab, On Selected zet de page state, On Tab Change reset beide, hint "Alle categorieën", volle breedte | |
| Loginvelden · drawer-logo · Crashlytics-console | ✅ ALLE DRIE AF 2026-09-19 (export-geverifieerd). ⚠️ Crashtest-knop opruimen blijft open: staat op Favorieten, pagina van 88B |
|
Ontbrekend asset logo_1080x2160.png |
✅ AF — opgehaald met --include-assets en gecommit |
|
| Header-locatie + "(gemeente)" weg | ✅ AF — A op productie (385 namen nagemeten), B export-geverifieerd | |
| G0.6 | Play-screenshots schieten | Browserloos op beide AVD's, ±10 min — zeg het en het gebeurt |
| # | Taak | Waarom nu |
|---|---|---|
| App-icoon | 🟢 Niet meer blokkerend (gemeten Claude 2026-09-19). De export wijst al naar PNG's en het logo staat op 64 % van de breedte: "Uitgaanskrant.com" is volledig leesbaar, "itgaanskrant.co" is weg. Rest is cosmetisch — zie het B1-blok | |
| Splashscreen | ✅ AF (Bob 2026-09-19, export-geverifieerd): nav.dart toont logo_1080x2160.png; op telefoon, 9:20, tablet staand én liggend volledig leesbaar |
|
| G | Google Play-checklist G0–G4 | Volledige checklist hieronder, afvinkbaar |
| 66 | Crashlytics: Firebase-console nakijken + Crashtest-knop opruimen | App-kant is bewezen (rapport met status 200); alleen de console-check en het opruimen zijn nog van jou |
| P2-24 B | Alleen nog: de Cloudflare-regelvoorwaarden over alle drie de groepen trekken | Kanttekening bij P2-24 B; niet blokkerend |
| P1-17 | Engelse vertalingen, één ronde | 46 velden op de twee aanmaakpagina's + 3 losse |
| Bij livegang | Zes schakelaars en opruimklussen | Eigen blok hieronder |
| Drupal | taak 56, P2-25 | Eigen blokken hieronder (P1-42/P1-43 zijn af, nagemeten 2026-09-19) |
#
EventCurrent is generiek voor evenement ÉN activiteit — één veld mist · Eigenaar: Bob (besluit) / Claude (uitvoering)Uitgezocht en gemeten 2026-09-19 (sessie 'taken-B1-B7'), n.a.v. Bob's vraag. Vastgelegd omdat een andere sessie hier ook aan werkte.
Bevestigd: EventCurrent werkt voor beide contenttypes.
EvenementCall = flutterflow_events.json?display_id=services_1&nid=X. De view
filtert op type IN (activity, go_out_event) — beide zitten erin.222158): de view geeft 'm gewoon terug.custom.module voegt de c/g-veldparen uit die view samen tot één uniforme
stream: logoc(field_logo2)/logog(field_logo4) → logo;
field_address1/field_address2 → adres; entreeprijsc/entreeprijsg →
entreeprijs; entreetoelichtingc/g → entreetoelichting;
websitec/websiteg → website; fotooc/fotoog → fotos.EventCurrent zit al achter
Visibility(visible: widget!.horecaid != null && widget!.horecaid != '') — bij een
activiteit is horecagelegenheidnid leeg en verdwijnt het blok vanzelf.adres, body, categorie,
contact, datum, datum_iso, entreeprijs, entreetoelichting, fotos,
horecagelegenheidnid, logo, nid, organisator, plaats, title, website.🔴 Het gat: organisator wordt NERGENS in de app getoond. Er is geen
eventOrganisator-getter op EvenementCall, geen parameter op EvenementInfo en
geen weergave. Het veld komt in de hele levende code alleen voor in
stadsactiviteit_create.dart (invoer) en in één helptekst. Een gebruiker vult bij
"Stadsactiviteit aanmaken" dus een organisator in die daarna onzichtbaar is.
Fix (klein): getter $[0].organisator op EvenementCall → parameter op
EvenementInfo → een Text met een Is Set-conditie (bij een evenement is het
veld leeg). ✅ Uitgevoerd 2026-09-19 na Bob's akkoord — zie het blok "99 / P2-46-deel ·
organisator op EventCurrent — AF" verderop. Dit deel van taak 99 is daarmee dicht;
alleen de kanttekening over categorie/activity_category hieronder staat nog open.
Twee dingen die verdacht lijken maar in orde zijn (niet "fixen"):
EvenementCall.eventLogog en eventWebsiteg dragen nog de oude g-suffix van
gooutevent, maar wijzen correct naar de samengevoegde $[0].logo /
$[0].website. Alleen de namen zijn historisch.EvenementInfo krijgt 12 parameters en leest er 6 (adres, plaats,
entreetoelichting, entreeprijs, website, contact). De andere zes (nid,
fotoos, title, date, categories, logo) worden wél getoond, maar
rechtstreeks op EventCurrent i.p.v. in de Info-tab. Rommel, geen zichtbaar gat —
dit is het P1-44-patroon maar hier onschadelijk.⚠️ Mogelijk nog te meten: de view zet op het categorie-veld (term_node_tid_1)
alléén de vocabulary go_out_event_category aan, niet activity_category. Of een
activiteit daardoor een lege categorie krijgt is niet nagemeten — de gemeten
activiteit had wel een categorie, maar dat bewijst niet dat het altijd goed gaat.
📍 Voortgang deel B (2026-09-19, sessie 'taken-B1-B7'):
MijnAanmeldingen aangemaakt en export-geverifieerd —
sessionName/sessid (String) + page/limit (Integer, default 0 en 50),
header Cookie: '${sessionName}=${sessid}' (echte interpolatie), query-params
page+limit From Variable, cache: false.MijnAanmeldingen aangemaakt (blanco), route /mijnAanmeldingen.CLAUDE.md: Info / Elevation 2 / Show Default Button
uit / 80 PX / Toolbar 25 % / Bottom Section / Center Title / Flexible Space /
Background Widget → HeaderButtonsComponent met showBackButton AAN).drawerComponent moet er nog in via
Edit Drawer → "+" op de Drawer-rij; zonder Edit Drawer registreert de klik niet).
Widget tree nu: MijnAanmeldingen → [Column, AppBar, Drawer].$, niet Predefined Path — gepagineerde lijst, zie CLAUDE.md)EventCurrent met twee
parameters: nid → $.nid én horecaid → $.horecagelegenheidnid.
⚠️ Geldt voor BEIDE types — EventCurrent is generiek (zie het blok 99
hieronder). Bij een activiteit is horecagelegenheidnid leeg en verdwijnt het
horeca-blok vanzelf.
⚠️ A1 geeft horecagelegenheidnid nog NIET terug — één regel toevoegen aan
custom.mijn_aanmeldingen.inc (het veld staat al in de veldkaart als horeca,
machine name field_horecagelegeheid1, let op de typo in die veldnaam).🔒 GECLAIMD 2026-09-19 door sessie 'profiel-hub' — bezig (zie taak 104). Deel A (beide Drupal-endpoints)
draait op productie, getest 2026-09-19: A1 geeft 122 aanmeldingen voor
bobhoreca, A2 geeft 200 op eigen nodes en 403 op andermans (bevestigd op
vier vreemde nids). ?limit= en ?page= werken allebei.
⚠️ Default limit is 50 — de app-lijst moet dus pagineren.
Bob 2026-09-18: "Ziet een horecagelegenheid-eigenaar of stadsrechten-eigenaar ook een overzicht van hun zelf aangemaakte dingen? En ik wil er een knop achter, klone, waardoor het formulier met de eerdere gegevens wordt gevuld."
Antwoord op de eerste vraag: nee, dat bestaat nog niet. Gemeten 2026-09-18 op
de export: de enige "mijn"-endpoints zijn mijn_horecagelegenheden.json (vult de
dropdown op uitgaansevenementAanmaken) en mijn_stadsrechten.json. Er is geen
enkele call die teruggeeft wat deze gebruiker heeft aangemaakt. Na het indienen
van een evenement verdwijnt het dus uit beeld: de gebruiker kan niet zien of het
erop staat, niet of het al goedgekeurd is, en niets hergebruiken.
A · Drupal (Bob) — twee endpoints, allebei sessie-geauthenticeerd
mijn_aanmeldingen.json (zelfde auth-patroon als
mijn_horecagelegenheden.json). Alle nodes van type go_out_event en
activity met uid = huidige gebruiker, nieuwste eerst. Per rij: nid,
type, title, datum_start (rauw yyyy-MM-dd HH:mm:ss), status (0/1),
plaats, logo (URL, voor een thumbnail).aanmelding_kloonbron.json?nid=X — sessie-auth plus een
eigenaarscheck ($node->uid == $user->uid, anders 403; zonder die check kan
iemand andermans gegevens uitlezen). Levert de rauwe waarden, niet de
weergavewaarden: horecagelegenheid_nid, titel, omschrijving (rauwe body),
entree_tid, entreeprijs, toelichting_entree, tickets_url,
website_url, categorie_tids (lijst), logo_fid, fotos_fids (lijst).Waarom A2 een eigen endpoint moet zijn en flutterflow_events.json niet
volstaat — gemeten op nid 214439: die view geeft categorie als labels
(['Tweedehands markt']) i.p.v. tids, logo/fotos als URL's i.p.v.
fids, datum als zaterdag 19 sep, 9:00 (geen jaar, niet parseerbaar),
website verpakt in een <a href=...>-tag, en er zit geen entree-tid in.
Daar is geen formulier mee te vullen.
✅ Besluit Bob 2026-09-19: een kloon hergebruikt dezelfde fid's. De
gebruiker hoeft dus niet opnieuw te uploaden. Aan de Drupal-kant regelt
file_field_insert()/file_field_update() de file_usage-registratie bij
node_save() zelf zodra de fids op het veld staan; alleen controleren dat
_custom_evenement_create() bestaande (permanente) fids accepteert en niet
op status = 0 filtert.
B · App (Claude, builder)
MijnAanmeldingen met de standaard header + Drawer
(recept staat in CLAUDE.md), een StaggeredView over A1 met Cross Axis Count
1/1/2/3 en een Empty List Widget. Per kaart: titel, datum, statuslabel
("Gepubliceerd" bij status: 1, "Wacht op goedkeuring" bij 0) en twee
knoppen: Bekijken (Navigate → EventCurrent met nid) en Klonen.ListTile erbij,
binnen de bestaande userSessionid-login-guard. Dupliceer een bestaande
rij (Ctrl+D) — dat is hier het bewezen patroon, Insert Widget niet.uitgaansevenementAanmaken. Niet via een page state die je in On Page Load
vult: Initial Option Value van een dropdown wordt tijdens de eerste build
geëvalueerd en On Page Load draait pas in een addPostFrameCallback — dan is
de FormFieldController al met de lege waarde geïnitialiseerd (staat zo in
CLAUDE.md).Nieuwe parameters op uitgaansevenementAanmaken, allemaal niet-verplicht
(een verplichte parameter genereert een ! en laat de gewone "nieuw"-route
crashen): kloonHorecaNid, kloonTitel, kloonOmschrijving, kloonEntreeTid,
kloonEntreeprijs, kloonToelichting, kloonTicketsUrl, kloonWebsiteUrl
(String), kloonCategorieTids + kloonFotosFids (List), kloonLogoFid.
Binden: elke TextField → Initial Value, elke dropdown → Initial Option
Value.
Bewust NIET klonen: de twee datumvelden. Je kloont juist om hetzelfde evenement op een níeuwe datum te zetten, dus een overgenomen datum is per definitie fout en moet alsnog weg. Twee lege datumvelden zijn meteen ook de rem tegen per ongeluk twee keer exact dezelfde aanmelding publiceren.
Volgorde. A1 + B1 + B2 (het overzicht) is op zichzelf al waardevol en veel kleiner dan de kloonknop — lever dat als eerste op, A2 + B3 daarna. Zonder het overzicht heeft de kloonknop trouwens geen plek om te staan.
Twee valkuilen vooraf. (1) De List<String>-parameters zijn het fragiele
deel: een List-typed argument dat op Unset blijft staan blokkeert de héle
export met een generieke 400 (zie CLAUDE.md) — loopt dat vast, dan is het een
Bob-stukje. (2) stadsactiviteitAanmaken heeft exact hetzelfde formulierpatroon;
bouw eerst evenementen helemaal af en kopieer daarna pas, niet allebei tegelijk.
MijnProfiel wordt de accounthub · Eigenaar: Claude — bezigAanleiding: Bob 2026-09-19 — "Waar staat het overzicht van eigen horecagelegenheden en eigen evenementen? In de drawer moet onderaan de user button naar het profiel gaan. De opties op Favorieten tab 'Gebruiker' moeten ook allemaal onder mijnprofiel komen."
Gemeten op een verse export (2026-09-19):
MijnProfiel (/mijnProfiel) bestaat, heeft al de standaard header +
Drawer, en toont twee FutureBuilders: MijnHorecagelegenhedenCall (kaarten)
en MijnStadsrechtenCall (platte tekstregels titel — parent_titel). Elk met
een kop en een "+ Voeg toe"-knop.Padding-widgets op die pagina: alles plakt tegen de schermrand.WachtwoordVergeten) · Account verwijderen (→ launchURL
https://uitgaanskrant.com/nl/support/mobieleapp) · Stadsactiviteit aanmaken.Besluiten Bob 2026-09-19:
MijnAanmeldingen (niet de volle lijst op het profiel — 122 rijen, en een
gepagineerde grid in de scrollende Column van het profiel rendert leeg).Stappen (in deze volgorde):
Navigate To van Favorieten naar
mijnProfiel. Uitgelogde tak blijft Login.MijnProfiel: login-guard op de inhoud + padding 16 + sectiekoppen.MijnProfiel (Wachtwoord wijzigen, Account
verwijderen, Uitloggen, Stadsactiviteit aanmaken) — dupliceren van een
bestaande rij is hier het bewezen patroon, niet Insert Widget.MijnAanmeldingenCall met
limit=5, shrinkWrap-ListView, plus knop Alle aanmeldingen →
/mijnAanmeldingen.MijnAanmeldingen afbouwen (StaggeredView 1/1/2/3,
JSON Path $, statuslabel, Bekijken → EventCurrent).Bob-stukjes die hierbij horen:
mijn_aanmeldingen.json geeft horecagelegenheidnid nog niet terug —
één veld erbij in custom.mijn_aanmeldingen.inc (machine name
field_horecagelegeheid1, let op de typo). Zonder dat mist het horeca-blok
op EventCurrent na doorklikken vanuit de lijst.Niet in deze ronde: de kloonknop (88 B3, 11 nieuwe page parameters) — eigen sessie.
Gevonden 2026-09-19 dankzij de zojuist ontsloten Crashlytics. Het dashboard
toont naast de bewuste testcrash een tweede issue:
PlatformAssetBundle.loadBuffer — Unable to load asset:
"assets/images/logo800px.png", com.uitgaanskrant.app, 3 events / 1 user,
versie 1.0.0, via asset_bundle.dart:373.
Diagnose (gemeten, niet aangenomen):
assets/images/logo800px.png bestaat in de projectmap (11 KB) en
assets/images/ staat in pubspec.yaml:183. Een correcte build heeft het dus.--include-assets bevat alleen favicon.png
(opnieuw bevestigd op /tmp/ff-chk3). Een APK daaruit crasht op dit asset —
precies het bekende patroon uit CLAUDE.md.ff-v3, ff-v4, ff-cr) hébben
het asset alle drie, dus de schuldige build is al opgeruimd.🔴 Maar het legde een tweede, echt gat bloot. De levende code gebruikt exact twee assets; één daarvan ontbreekt in de projectmap:
| gebruikt in de code | in assets/images/? |
|---|---|
logo800px.png |
✅ |
logo_1080x2160.png (de splash uit nav.dart, taak B1b) |
❌ ontbreekt |
FlutterFlow host het plaatje in de cloud, dus de builder en een export mét
--include-assets hebben het gewoon (/tmp/ff-v3 had het). Alleen de lokale
projectmap is sinds Bob's upload nooit met die vlag ververst. Wie uit de
projectmap lokaal bouwt, krijgt dus een app die op de splashscreen crasht.
Wat er moet gebeuren:
flutterflow export-code ... --include-assets en de map
assets/images/ bijwerken, zodat de repo compleet is. (Claude kan dit; het
raakt alleen assets/, geen lib/.)--include-assets verplicht
en controleer je vóór het bouwen ls assets/images/ op méér dan favicon.png.
Neem dit op in het besluit van G0.3.
✅ Stap 1 is gedaan (Claude, 2026-09-19): logo_1080x2160.png (845 KB) is met
--include-assets opgehaald en staat nu in assets/images/. Beide door de code
gebruikte assets zijn aanwezig; de projectmap is compleet.📌 Bijvangst — adaptive icon nagemeten en gerenderd zoals Android het toont.
adaptive_foreground_icon.png is byte-identiek aan app_launcher_icon.png
(md5 7b0b74a3…), 1900x1900, 0 % transparant, buitenste 17 % volledig
ondoorzichtig. Daardoor is adaptive_icon_background: '#9a141d' onzichtbaar.
Ik heb het zichtbare deel (middelste 66,7 %) uitgesneden en er een rond en een
squircle-masker overheen gelegd: de tekst blijft volledig leesbaar — Bob's
B1-meting klopt — maar bij een rond masker worden de afgeronde uiteinden van
de witte pil eraf gesneden en raakt het logo de rand. Cosmetisch, niet
blokkerend; B1 blijft dus terecht op groen. Wil je het netter: een foreground
met het logo op ~55 % van de breedte op een transparant vlak, dan doet
#9a141d zijn werk en is er marge bij elk maskertype.
A (Drupal, Bob) staat op productie en is nagemeten · B (app, Claude) is
export-geverifieerd met dart analyze 0 errors. Enige restpunt: nog niet op
een toestel gezien, en "Heel Nederland" vertaalt niet (hoort bij P1-17).
Bob 2026-09-19: "ja, goed plan a" + "B. Goed plan." Beide goedgekeurd.
A-patch (_custom_gemeentenaam_kort() + één regel in custom_retrieve_hierarchy())
is als bestand geleverd en getest tegen alle 385 echte namen: 237 gewijzigd,
148 ongemoeid, 0 duplicaten, niets leeg. Na deploy: drush cc all plus
Cloudflare purgen — plaatsen.json zit in de 2-uurs Cache Rule.
Twee dingen die dezelfde Text in HeaderButtonsComponent raken; daarom samen.
Ontdekt tijdens het schieten van de Play-screenshots (2026-09-19).
Meting 1 — de header toont ALTIJD de gemeentenaam. HeaderButtonsComponent
regel 239 bindt hard aan FFAppState().gemeenteSelectNaam. En:
provincieSelectName wordt in de hele levende code wel gezet, nergens
getoond (3 schrijfplekken, 0 leesplekken). Antwoord op Bob's vraag "staat er bij
provincie wel provincie?": nee.
Waar dat misgaat:
| pagina | content is | header zegt |
|---|---|---|
| Home | landelijk — geeft alleen displayid door, géén townid | Amsterdam (gemeente) ❌ |
| PUitgaanPage via Provincie-menu | provincie (plaats: provincieSelectId) | Amsterdam (gemeente) ❌ |
| PUitgaanPage via Gemeente-menu | gemeente | klopt ✅ |
| horeca, detailpagina's | gemeente | klopt ✅ |
Beide menugroepen gaan naar dezelfde pagina (PUitgaanPage), alleen met een
andere plaats-parameter — die pagina weet dus zelf niet wat hij toont.
Meting 2 — "(gemeente)" zit NIET op alle namen. Alle 12 provincies uitgelezen, 385 gemeenten:
" (gemeente)" → kan schoon weg" (gemeente NH)" / " (gemeente LB)" — ⚠️ die suffix is betekenisvol:
er zijn twee gemeenten Bergen. Kaal strippen geeft twee identieke regels in de
dropdown.Alle 12 provincies opgehaald, 385 gemeenten: 0 met (gemeente, 0
duplicaten, 0 leeg. Alleen Bergen (LB) en Bergen (NH) houden hun haakjes
— precies de bedoeling, anders stonden er twee identieke regels "Bergen" in de
dropdown. Amsterdam heet nu gewoon Amsterdam.
oorspronkelijk voorstel A
VOORSTEL A — "(gemeente)" weg. In Drupal, niet in de app.
Eén bewerking op gemeentename in plaatsen.json:
" (gemeente)" -> "" (235 namen)" (gemeente " -> " (" (2 namen: Bergen (gemeente NH) -> Bergen (NH))Waarom aan de Drupal-kant: de naam wordt op twee plekken getoond (de
gemeente-dropdown én de header) maar komt uit één bron. Eén functie daar dekt
allebei, en er komt geen custom function bij die Claude later niet meer mag
weggooien. Kost geen app-wijziging en geen export.
⚠️ gemeenteSelectNaam staat in secure storage, dus een bestaande installatie
houdt de oude naam tot de gebruiker opnieuw kiest. Bij een nog niet gepubliceerde
app irrelevant.
VOORSTEL B — de header laten kloppen. 2 pagina's, niet 19.
De header staat op 19 pagina's; de truc is dat de defaults de overige 17 ongemoeid laten.
HeaderButtonsComponent:
landelijk (Boolean, default false) en plaatsId (String, optioneel).Eén custom function, plat geschreven (geen geneste closures — zie CLAUDE.md):
String headerLocatie(bool? landelijk, String? plaatsId, String? provincieId,
String? provincieNaam, String? gemeenteNaam) {
if (landelijk == true) return 'Heel Nederland';
if (plaatsId != null && plaatsId.isNotEmpty &&
plaatsId == provincieId &&
provincieNaam != null && provincieNaam.isNotEmpty) return provincieNaam;
if (gemeenteNaam != null && gemeenteNaam.isNotEmpty) return gemeenteNaam;
return 'Kies je gemeente';
}
5 argumenten — precies de grens die volgens CLAUDE.md bewezen volledig
bereikbaar is in "Set Function Arguments".
Text in de header (vervangt de huidige valueOrDefault).landelijk = true. PUitgaanPage: plaatsId = de bestaande page
parameter plaats. Verder niets.Resultaat: Home -> Heel Nederland · Provincie-menu -> Noord-Holland · Gemeente-menu en alle andere pagina's -> Amsterdam · niets gekozen -> Kies je gemeente.
⚠️ Eerlijke kanttekening: "Heel Nederland" komt uit een custom function en
gaat dus niet door FFLocalizations — in de Engelse modus blijft die ene
tekst Nederlands. Zelfde categorie als 65e; hoort bij P1-17.
dart analyze 0 errorsAlle vier de stappen staan en zijn nagelezen op een verse export:
headerLocatie (custom_functions.dart:416): return
String non-nullable — daardoor genereert FlutterFlow geen
valueOrDefault-wrapper en is er geen default-tekst nodig. 5 nullable
argumenten.HeaderButtonsComponent:
this.landelijk (bool?) en this.plaatsId (String?). Beide
niet-required, dus de 17 andere pagina's geven null door en houden
exact hun huidige gedrag.Text is gebonden:
functions.headerLocatie(widget!.landelijk, widget!.plaatsId,
FFAppState().provincieSelectId, FFAppState().provincieSelectName,
FFAppState().gemeenteSelectNaam).landelijk: true. PUitgaanPage: plaatsId: widget!.plaats.Resultaat: Home -> Heel Nederland · Provincie-menu -> de provincienaam · Gemeente-menu en alle andere pagina's -> de gemeentenaam · niets gekozen -> Kies je gemeente.
⚠️ Twee restpunten:
FFLocalizations — in de Engelse modus blijft die ene tekst Nederlands.
Zelfde categorie als 65e; hoort bij P1-17.dart analyze zijn schoon.📌 Bijvangst: PUitgaanPage zet HeaderButtonsComponent in de title-slot
van de AppBar, terwijl elke andere pagina 'm in FlexibleSpaceBar.background
zet. Niet aangeraakt (het werkt), maar het is dezelfde soort afwijking als 65b.
⚠️ Nieuwe les voor CLAUDE.md (2 keer misgegaan): in het Function-Settings-
paneel moeten "+ Add Arguments" en de klik op het Name-veld in APARTE
tool-aanroepen. In één batch landt de Name-klik vóórdat het nieuwe blok
gerenderd is, en dan belandt je tekst in Description — zichtbaar als een
/// <naam>-regel bóven de functie in de editor. Herstel: Description leegmaken
(klik, ctrl+a, Delete). Werkwijze die wél elke keer werkte: Add Arguments in
call A, zoom om de echte y-positie van het Name-veld te lezen, dan in call B
twee keer klikken (4 s ertussen) en typen.
Goedkopere variant als B te veel is: alleen landelijk doen (1 parameter,
1 pagina, geen plaatsId). Dan klopt Home — het scherm dat iedereen als eerste
ziet — en blijft alleen het provincie-geval scheef.
(P2-45b, P2-45c en 76 zijn op 2026-09-19 afgerond en geverifieerd met een verse export — zie de commit van die datum.)
*Opgesteld 2026-09-19. Account aangemaakt 2026-09-18 als Organisatie (Digital Force); wacht op verificatie. Vervangt taak 63 (a-i) en punt 2 van "Bob's 7" — die twee niet meer los afwerken. Vink hier af.*
Bevestigd via Google's eigen documentatie, 2026-09-19:
#
flutter_launcher_icons, en een crash op de splash zonder
--include-assets), plus een keystore die er nog niet is
(android/app/build.gradle:80 staat op signingConfigs.debug).
Sub-route: download het AAB in FlutterFlow en upload het handmatig in
Play Console. De directe "publish to Play"-koppeling vergt een Google
Play service-account-JSON; pas doen als handmatig uploaden gaat vervelen.
⚠️ Blijft staan als G4.3: controleer het icoon op het AAB dat je uploadt.~/uk-play-assets/play-teksten.md (gered uit een
vluchtige sessie-scratchpad, 2026-09-19 — stond nergens duurzaam).
⏳ Kan pas ingevoerd worden ná G1.1 (de app moet bestaan in Play Console).--release-build (export mét --include-assets, flutter_launcher_icons
vooraf): 6 telefoon (1080x1794), 2x 7" tablet (1200x1805), 2x 10" tablet
(2560x1504). Systeembalk weggecropt; alle formaten binnen Play's eisen
(320-3840 px, ratio onder 2:1).
📁 Staan nu duurzaam in ~/uk-play-assets/screenshots/ (+ de zip ernaast);
ze stonden alleen in een sessie-scratchpad onder /tmp en waren een reboot
kwijt geweest. Gered 2026-09-19.
🔴 De set is NIET uploadbaar zoals hij is — drie defecten, gemeten 2026-09-19:
tab10-01-home.png en tab10-02-menu.png zijn byte-identiek
(md5 b34debb5…, beide de Home-pagina). De menushot is nooit geland, dus
er is feitelijk één unieke 10"-tabletscreenshot. Play wil er meer dan
één per formaat; controleer het exacte minimum in Play Console.tel-04-horeca.png toont de AdMob-testadvertentie ("This is a
320x50 test ad"). Zet Show Test Ads uit (staat op de livegang-lijst).tel-05-zaak.png toont een zaak met een lege agenda; klopt
inhoudelijk, maar een zaak mét agenda oogt sterker.
🔴 En: álle tien tonen de koptekst Amsterdam (gemeente) — precies wat
taak 103 aan het repareren is. Elke shot bevat die header, dus de hele set
moet opnieuw zodra 103 gereed is. Niet uploaden vóór die tijd.
➡️ Eén herschietronde ná 103 + Show Test Ads uit dekt alle vier de punten.#
com.uitgaanskrant.app — onveranderlijk na de eerste
upload, dus controleer 'm.#
https://uitgaanskrant.com/nl/support/privacybeleid
(anoniem 200, gemeten 2026-09-16).https://uitgaanskrant.com/nl/support/mobieleapp
⚠️ NIET /nl/account-verwijderen — die geeft anoniem 403 en reviewers
openen 'm uitgelogd. Taak 85 heeft de in-app-knop al naar dezelfde URL gezet.INTERNET, CAMERA, READ_EXTERNAL_STORAGE,
WRITE_EXTERNAL_STORAGE, plus via AdMob/Firebase ACCESS_NETWORK_STATE,
WAKE_LOCK, FOREGROUND_SERVICE, AD_ID en drie ACCESS_ADSERVICES_*.
Declareren: Advertising ID → JA (verplicht, AD_ID staat erin),
crashlogs/diagnostiek (Crashlytics), gebruikersnaam/e-mail (login), en
foto's (camera + galerij, voor de aanmaakpagina's). Transport is
versleuteld en verwijdering is mogelijk → beide aanvinken.#
assets/images/app_launcher_icon.png
(het volvlakse), niet de adaptive foreground.#
1.0.0+1
(pubspec.yaml:18). Bij de FlutterFlow-route: App Settings → App Details.flutter_launcher_icons wél? Lokaal
gebouwd draagt het bestand anders het Flutter-vogeltje. Niet aannemen.#
Ja, betaald — en anders dan Google een ABONNEMENT. Google Play is eenmalig $25; het Apple Developer Program kost ~$99 per jaar en moet elk jaar verlengd worden, anders verdwijnt de app uit de App Store. Verifieer het actuele bedrag bij inschrijving.
Wat je al hebt en meeneemt:
Info.plist staan al goed (Camera, PhotoLibrary,
Calendar) — geverifieerd 2026-09-19, precies 3 keys.Wat er nieuw bij komt:
ios/Runner.xcodeproj/project.pbxproj
beweegt mee in elke export.Route wanneer het zover is: FlutterFlow kan ook voor iOS bouwen, dus een Mac is niet strikt nodig. Punt 5 en 6 zijn het echte werk; de rest is administratie.
mijn_aanmeldingen.json en aanmelding_kloonbron.json zijn geplaatst en
getest. Details staan in het 88-blok hierboven; deze taak is klaar.
De keten werkt, end-to-end gemeten op een profile-build op emulator-5554:
tik op Crashtest → FirebaseCrashlyticsTestCrash in logcat, proces weg →
app opnieuw geopend → Making request to crashlyticsreports-pa.googleapis.com
→ Status Code: 200. Het rapport is dus daadwerkelijk verstuurd.
🔴 En meteen de verklaring waarom er nooit een crash in het dashboard stond:
Bob's netwerk-DNS (192.168.8.220) blokkeert crashlyticsreports-pa.googleapis.com
(antwoordt 0.0.0.0). Elk rapport vanaf dit netwerk verdween in een sinkhole.
De koppeling was al die tijd in orde. Details + de omzeiling staan in
CLAUDE.md. emulator-5554 staat nu op private DNS (dns.google) zodat
Crashlytics daar werkt — terugzetten met
adb shell settings put global private_dns_mode off.
Voor Bob, in Firebase: zie het antwoord in de chat — kort: Crashlytics
openen, wachten tot de eerste crash het dashboard "activeert", en controleren
dat het bundle_id com.uitgaanskrant.app is. Daarna de Crashtest-knop weer
weghalen.
⚠️ Bob's huidige knop hangt aan de UITLOG-keten. De Duplicate nam alle
zeven uitlog-acties mee en crashlyticsTest staat er als achtste achter; je
wordt dus eerst uitgelogd en naar Login gestuurd, en pas daarna crasht hij. Het
werkt, maar voor een schone stack trace: Actions → Edit → acties 1 t/m 7
verwijderen (⋮ → Delete Action).
Nog open (los van deze taak): main.dart:44 zet alléén
FlutterError.onError. Er is geen PlatformDispatcher.instance.onError,
dus een fout in een async custom action buiten het framework wordt niet
gerapporteerd. Eén regel, maar main.dart is gegenereerd — moet via
FlutterFlow's Final Actions/custom-code-hook.
crashlytics_test.dart — één custom action crashlyticsTest(String? modus),
argument mag Unset blijven (String mag dat; alleen een List-typed argument
blokkeert de export). Drie standen: Unset → echte crash, melding → niet-fatale
melding, flutter → via de FlutterError.onError-koppeling uit main.dart.
Gemeten gat in de huidige opzet: main.dart:44 zet alléén
FlutterError.onError. Er is geen PlatformDispatcher.instance.onError,
dus een fout in een async custom action buiten het framework wordt niet
gerapporteerd. Zeg het als dat erbij moet — dat is één regel in main.dart,
maar main.dart is gegenereerd, dus het moet via FlutterFlow's
Final Actions/custom-code-hook, niet lokaal.
Verwachting bij het testen: een echte crash wordt pas bij de volgende
start van de app geüpload. Een melding gaat meteen weg en verschijnt onder
Non-fatals. Reken op ~5 minuten tot het in het dashboard staat.
Eerste echte --release-build ooit, uit een verse export mét --include-assets
(/tmp/ff-rel, fvm use 3.35.7 -f, dart analyze 0 errors), geïnstalleerd op
emulator-5556 (telefoon; 5554 was bezet door de sessie van taak 89).
dumpsys bevestigt flags=0x0 — geen DEBUGGABLE, dus een echte release.
✅ Wat goed gaat. De app start schoon op: 0 FATAL, 0 E/flutter in
logcat over de hele sessie. Data laadt (Amsterdam, slider, kaarten met foto's),
Nederlandse locale klopt, zoekveld + datumchips + vijf tabs staan er, en alle
iconen renderen — óók het enige FontAwesome-icoon (flagCheckered bij
Activiteiten in het menu), dat de tree-shaking dus overleeft. Het
tree-shaking-risico uit de oorspronkelijke taakomschrijving is hiermee van tafel:
er zijn 0 dynamisch opgebouwde IconData( in de hele levende code, en
fa-solid-900.ttf krimpt netjes van 419 KB naar 1248 bytes.
Screenshots: scratchpad/rel-home.png, rel-menu.png.
🔴 BLOKKEREND — een lokaal gebouwde release draagt het Flutter-standaardicoon.
Het blauwe Flutter-vogeltje, niet het rode merkicoon. Hard aangetoond, niet
afgeleid: het icoon uit app-release.apk is pixel-identiek aan de
Flutter-default (68 % #40c0e0, 31 % dekking) terwijl app_launcher_icon.jpg
83 % #a00000 en 100 % dekking heeft; visueel bevestigd, het is letterlijk het
Flutter-logo.
Oorzaak. Een verse export heeft android:icon="@mipmap/ic_launcher", alleen
de default ic_launcher.png (md5 57838d52…), geen mipmap-anydpi-v26/ en
geen values/colors.xml. Het blok flutter_launcher_icons: in pubspec.yaml
is wél correct ingevuld (image_path, adaptive_icon_background: '#9a141d'),
maar niemand voert die generator uit — flutter build doet dat niet.
Fix, één commando in de exportmap vóór het bouwen: ```
fvm flutter pub run flutter_launcher_icons
Dat herschrijft het manifest naar `@mipmap/launcher_icon`, genereert de
merkiconen, `mipmap-anydpi-v26/launcher_icon.xml` en `colors.xml`. Na die stap
opnieuw gebouwd en geverifieerd: `aapt2 dump badging` geeft dan
`application-icon-640:'res/IO.xml'` (een adaptive icon) i.p.v. een PNG.
⚠️ **Open vraag die iemand met Play-toegang moet beantwoorden:** doet
FlutterFlow's eigen Deploy-pipeline (de AAB die je daar downloadt) deze stap wél?
Waarschijnlijk, maar niet te meten vanaf hier. **Controleer het icoon dus op het
artefact dat je daadwerkelijk uploadt**, niet op dit lokale experiment. Bouw je
lokaal, dan is de generator-stap verplicht.
**🟠 Het adaptive icon snijdt de merknaam af — "itgaanskrant.co".** Ook ná de
generator. `adaptive_icon_foreground` wijst naar een **JPEG** (1408×1408), en een
JPEG kan geen transparantie hebben: de gegenereerde
`drawable-xxxhdpi/ic_launcher_foreground.png` is 432×432 met **0 % transparante
pixels** en de buitenste 17 % is **100 % ondoorzichtig**. Android toont van een
foreground alleen de middelste ~66 % en maskeert die rond. Gevolg: de
achtergrondkleur `#9a141d` is nooit zichtbaar (de foreground dekt alles af) en de
witte pil met de naam loopt aan beide kanten van het icoon af — de **U** en de
**m** vallen weg. Gerenderde preview: `scratchpad/adaptive-zoals-android-toont.png`
naast `icoon-bron.png`.
*Fix:* lever als `adaptive_icon_foreground` een **PNG met transparantie** waarin
alleen het beeldmerk op ±60 % van het vlak staat, gecentreerd; de rest
transparant. `image_path` (het gewone icoon) mag het huidige volvlakse plaatje
blijven. Hoort bij dezelfde ronde als taak 79.
**Kleine waarnemingen, geen actie nodig tenzij je wilt:**
- APK is **95,6 MB**. Voor Play upload je een AAB, die splitst per architectuur en
wordt fors kleiner — maar het is groot genoeg om even naar te kijken.
- Het manifest van de gebouwde app bevat naast de vier eigen permissies ook
`ACCESS_NETWORK_STATE`, `WAKE_LOCK`, `FOREGROUND_SERVICE`, `AD_ID` en drie
`ACCESS_ADSERVICES_*` — die komen uit AdMob/Firebase. Relevant voor taak 63(h):
**Advertising ID → ja** is dus verplicht, dat klopt met wat daar staat.
- De **UMP-consent-SDK van AdMob zit al in de build** (logcat probeert
`fundingchoicesmessages.google.com` te bereiken). Raakt P1-50 — de SDK is er,
alleen de flow niet.
- **Crashlytics initialiseert echt**: haalt zijn settings op met `status:
activated` en het juiste `bundle_id`. Dat is nog geen bewezen crashrapport
(P1-60), maar de koppeling leeft.
- Cosmetisch: het logo boven in het **menu (Drawer)** staat deels ónder de
statusbalk — de klok valt erachter. Zie `rel-menu.png`.
**✅ Functionele doorloop GEDAAN 2026-09-19 (Claude), op een verse
`--release`-build (`flags=0x0`) uit een export van diezelfde dag, mét
`flutter_launcher_icons` vooraf, op de telefoon-AVD `emulator-5554`
(1080x1920).** ⚠️ De poorten waren omgedraaid t.o.v. wat hierboven staat: 5554
was de telefoon, 5556 de tablet — check dat altijd met `wm size`.
**Geen enkele crash.** 0 `FATAL` / 0 `E/flutter` over de hele doorloop: opstarten,
Home, datumfilter, evenement-detail, drawer, horeca-overzicht, zaakpagina,
Favorieten (uitgelogd), loginpagina, vliegtuigmodus en Engels.
**Wat werkt (geverifieerd op het scherm):** Home met slider, kaarten en foto's ·
datumkiezer kiezen én annuleren (P2-45b, zie hieronder) · chips · alle vijf de
tabs · drawer met alle iconen · horeca-overzicht met zoekveld, zes tabs en
naast elkaar lopende categorielabels · zaakpagina met fotocarousel, vier tabs
(Agenda eerst) en een nette lege-agenda-melding · AdMob-testadvertentie ·
Favorieten-tab *Gebruiker* met uitleg + "Account aanmaken" + "Inloggen" ·
loginpagina · **vliegtuigmodus: geen crash, de `VerbindingsBanner` verschijnt
met "Geen verbinding met uitgaanskrant.com" en een knop *Opnieuw*, en de
fallback-logo's vullen de lege lijsten** · **Engels: chips en tabs vertalen
netjes** (All/Today/Weekend/This week, Go Out/Cityactivities/Culture & Info).
**🔴 Nieuwe bevindingen uit deze doorloop — allemaal cosmetisch/UX, geen crash:**
- **✅ 65a · Loginpagina links afgeknipt — OPGELOST 2026-09-19 (Claude).** De
witte velden begonnen op x=0 (afgeronde hoek viel weg) en het hele formulier
stond links terwijl de rechterhelft leeg bleef. *Oorzaak:* de `Stack` op deze
pagina heeft maar **één** kind, dus de `Column` eronder krijgt losse
breedtebeperkingen en wordt precies zo breed als zijn breedste kind — de twee
`Container`s van 200 px. De pagina-`Column` was dus 200 van de 393 px breed en
stond links. *Fix:* op die buitenste `Column` **Alignment X=0, Y=-1**
(top-center) plus **Cross Axis Alignment = center**. Export:
`Align(alignment: AlignmentDirectional(0.0, -1.0), child: Column(…
crossAxisAlignment: CrossAxisAlignment.center))`. `dart analyze` 0 errors.
⚠️ **Let op — `crossAxisAlignment` alléén doet hier NIETS**, want de Column
heeft geen overtollige breedte om in te centreren; de `Align` eromheen is wat
het werk doet. Ditzelfde patroon (Stack met één kind → alles plakt links) kan
elders in het project ook voorkomen.
*Restpunt — ✅ AF als taak 99 (Bob, 2026-09-19, export-geverifieerd).* De twee
invoervelden en de knop *Inloggen* staan nu op `double.infinity`, met
`fromSTEB(24, 0, 24, 0)` op de `Column` in de `Stack`; `width: 200` komt 0x meer
voor. ⚠️ **Les voor een volgende keer: er is GEEN `Container`-node in de tree** —
FlutterFlow zet `Width` als property op het `TextField` zelf en genereert de
`Container(width:)` pas in de export. Zoek dus niet naar een Container.
- **✅ 65b · Header schoof naar links op `HorecagelegenheidCurrent` — OPGELOST
2026-09-19 (Claude).** De hamburgerknop stond met zijn centrum op **x=53**
(elders x=271/x=340) en stak dus links buiten beeld. *Oorzaak, gevonden door
de twee pagina's naast elkaar te leggen:* deze pagina zet
`HeaderButtonsComponent` in een **extra `Row`** binnen
`FlexibleSpaceBar.background`, terwijl elke andere pagina het component daar
rechtstreeks in zet. Het component is intern `Align > Row` en centreert zichzelf
over de volle breedte; als kind van nóg een Row belandt het links op positie 0.
*Fix zonder iets weg te gooien:* op die extra Row **Main Axis Alignment =
center**. Export: `Row(mainAxisSize: max, mainAxisAlignment:
MainAxisAlignment.center, …)`.
**Op het toestel nagemeten:** knopcentrum van **x=53 naar x=270** — gelijk aan
de andere pagina's (x=271). **Controleer dit patroon bij een nieuwe pagina:**
`grep -n "background: Row(" lib/**/*_widget.dart` hoort alleen deze pagina te
geven — overal elders staat er `background: wrapWithModel(`.
- **65c · Favorieten toont uitgelogd een volledig LEEG scherm.** De standaardtab
*Persoonlijke agenda* rendert niets: geen melding, geen uitleg, geen
inlogknop. De uitleg + inlogknop staan op tab 4 (*Gebruiker*), maar die valt
buiten beeld in de scrollende tabbalk — een uitgelogde bezoeker vindt 'm niet.
**Onderzocht 2026-09-19 (Claude) — de voor de hand liggende route werkt niet:**
de **Empty List Widget**-sectie van deze lijsten biedt als *Widget Type* alleen
**Image** en **Component**, géén **Text**. Een geschreven uitleg kan er dus niet
rechtstreeks in. (Toggle is weer uitgezet; het project staat zoals het stond.)
**Twee routes die wél kunnen, allebei met een tekstkeuze die van Bob is:**
(a) een klein component maken met de uitleg + de knoppen *Account aanmaken* /
*Inloggen* (die staan al inline op tab 4, dus de inhoud bestaat) en dat als
Empty List Widget op tab 1-3 zetten — meteen consistent op alle drie; of
(b) per tab een `Text` ónder de lijst met een Visibility-conditie op
`userSessionid` *Is Not Set or Is Empty* (die operator bestaat, zie `CLAUDE.md`).
Route (a) is netter omdat hij óók werkt voor een ingelogde gebruiker zonder
favorieten.
- **🟡 65d · Deelknop op `EventCurrent` — KLEUR OPGELOST, padding nog open.**
De knop had `fillColor: Color(0xFFF265A0)`, een hardcoded roze dat niet in het
merkpalet zit. **Gezet op thema-token `Secondary`** (#FF680D, de merkregel is
"oranje is wat je aanklikt"); `#F265A0` komt nu **0×** in het hele project voor.
Geverifieerd met een verse export én op het toestel (knop is nu oranje).
⚠️ **Rest voor Bob, 10 seconden:** de knop raakt nog steeds de rechter
schermrand. `EventCurrent` → widget tree filteren op `IconButton` → de
`IconButton` onder `Row > Tooltip` → sectie **Padding** → **R = 12**. Claude
komt niet bij dat veld: het ligt tegen de rechter paneelrand, drie pogingen
landden niet en één klik opende per ongeluk de variabele-binder (zonder iets
te binden — netjes geannuleerd).
- **65e · "Datum" blijft Nederlands in de Engelse modus.** Het label van de
kalenderknop komt uit de `valueOrDefault`-default `'Datum'` en is dus géén
vertaalsleutel. Hoort bij P1-17.
- **✅ Taak 100 — AF (Bob, 2026-09-19, export-geverifieerd: `fromSTEB(0.0, 40.0, 0.0, 5.0)`).** Het **logo in de drawer
staat deels onder de statusbalk** (de klok valt erachter). Gemeten op de verse export:
`drawerComponent` is `SingleChildScrollView > Column > Row > Expanded > Padding(0,8,0,5) >
ClipRRect > Image.asset(logo800px, 300x42)`, **0 SafeArea**. Fix = padding Top 8 -> 40.
**Nog steeds open van deze taak:** alles waarvoor je ingelogd moet zijn — hartje
op een zaakpagina, Favorieten met inhoud, evenement aanmaken — plus Bob's Xiaomi.
⚠️ **Claude voert geen wachtwoorden in, dus het ingelogde deel is per definitie
Bob's stuk.** De loginpagina zelf is geverifieerd tot aan het invoerveld.
---
#
## 🚦 Bij livegang — schakelaars en opruimklussen · Eigenaar: Bob
- **Builder-opruimronde P2-7, restant (Bob, 2026-09-17: "zet de rest bij
livegang").** Nog in de export van 2026-09-17 00:10: componenten
`SliderUitgaanComponentSmallCurrent` en
`PUitgaantabelKaartComponentOrgineelMetKaartjeerin`; pagina's
`KanwegTestUpload` en `Event` (die laatste weigert structureel, laten
staan); custom function `dISABLEdatumVoorApi` (hernoemd, nog steeds dood).
Weg zijn sinds 2026-09-16/17: `drawerComponentCopy`,
`kanweghorecagelegenheidCurrentCopy`, `KanwegHorecagelegenhedenOverzicht`,
`KanwegHomeCopy`, `HeaderButtonsComponentCopymethartje`, de drie
`KaartTabel/Slider`-comps, `KanwaegSelectStateDropDownComponentCopy`,
`KanwegHomeUitgaantabelKaartComponentCopy`, `KanwegHeaderButtonsComponentCopy`,
`FavorietenCopy`, `HorecagelegenhedenOverzichtCopy/Copy2/Copy2Copy`,
`Kanweg`, `KanwegHorecagelegenhedenOverzichtSortPage`. `dart analyze` op
die export: 0 errors.
- **✅ Taak 56 · Opruim-cron — GROTENDEELS AF (Bob, 2026-09-19).** Grens gezet op
**180 dagen**, `range` van 500 naar **1000**, en `$date_after_30` hernoemd naar
`$date_events_after`.
🔴 **Eén restpuntje: de `description` in `custom_cronapi()` zegt nog "after 360
days"** — dat was nu juist het punt van deze taak. Naar *"after 180 days"*.
**Impact gemeten door Claude op productie (2026-09-19, 17.123 unieke events via
`datum_iso`):** 6.728 toekomstig · 8.107 jonger dan 180 dagen · **2.251 tussen
180 en 360 dagen** · 14 ouder dan 360. Die 2.251 worden dus bij de komende runs
alsnog opgeruimd; met 1000 per run en één run per dag is dat **~3 dagen**.
⚠️ Twee dingen om in de gaten te houden: (1) mijn telling gebruikt de
**startdatum** (`datum_iso`), terwijl de cron op `field_date_value2` (de
**eind**datum) filtert — voor meerdaagse events ligt het echte aantal dus iets
lager; (2) `node_delete_multiple()` laadt in D7 elke node en draait alle hooks,
dus de eerste drie runs zijn fors zwaarder dan wat deze cron ooit deed (er
stonden maar 14 kandidaten). Kijk of hij niet in een timeout loopt.
- **AdMob: `Show Test Ads` uitzetten** (App Settings → AdMob). Staat nu AAN,
dus de banner op `pUitgaanPage` toont testadvertenties en levert niets op.
De GDPR-consentflow staat sinds 2026-09-15 wél goed (`adMobRequestConsent()`
in `main.dart`), dus het is alleen deze schakelaar nog.
- **P1-17 · alle Engelse vertalingen in één ronde** (Bob's besluit
2026-09-10: niet nu, want er komen onderweg nog teksten bij). Nu open:
46 velden in `stadsactiviteitAanmaken` (27) en
`uitgaansevenementAanmaken` (19), plus de twee nieuwe teksten uit taak 22
op `SelectStateDropDownComponent`: **"Gemeente volgen"** (`nosgkxy0`) en de
uitlegzin **"Bij favoriete gemeenten krijg je een agenda per mail met daarin
alle evenementen uit die favoriete gemeenten."** (`24bdnkhi`), en sinds
2026-09-15 ook **"Je volgt deze gemeente"** (`elx7kse2`) — let op: die kreeg
door `Duplicate` de *verkeerde* EN-tekst "Add to favorites" mee, dus niet
leeg maar fout; `nosgkxy0` staat in EN ook nog op "Add to favorites" en
hoort "Follow municipality" te worden.
- **Titels met spaties vooraan/achteraan opschonen (was P1-47, verbreed
2026-09-11).** Bob mat op devbob **100+** `horecagelegenheid`-nodes met een
gewone ASCII-spatie vóór de titel (`LIMIT 100` liep vol, dus het werkelijke
aantal is hoger); ook tabs (nid 49539) en spaties áchteraan (49512) komen
voor. MySQL sorteert op de rauwe `node.title`, dus elke zo'n node valt
buiten de alfabetische volgorde — terwijl de JSON schoon oogt, want
`_custom_clean_html` poetst ná de query. **Bob's besluit 2026-09-11: de
impact is nu te groot, we lopen de namen door bij de livegang.** Te
verifiëren op productie met de telquery hieronder; de drie losse titels uit
taak 16 zijn daar al gefixt, de rest vermoedelijk niet.
drush @prod sqlq "SELECT type, COUNT(*) FROM node WHERE title <> TRIM(BOTH ' ' FROM TRIM(BOTH CHAR(9) FROM TRIM(BOTH CHAR(10) FROM TRIM(BOTH CHAR(13) FROM TRIM(BOTH ' ' FROM title))))) GROUP BY type;"
De fix is een `UPDATE` met diezelfde genestelde `TRIM` op **`node` én
`node_revision`**, idempotent, met vooraf een `sql-dump` van die twee
tabellen. URL-aliassen blijven ongemoeid.
- **P2-7 · opruimronde, 23 dode eenheden** (Bob 2026-09-13: naar livegang).
⚠️ **Twee nieuwe sinds 17 sep, allebei van Claude:** de pagina
**`favorietenCopy2`** (oefenkopie van `Favorieten`, gemaakt om de
Wrap-Widget-route voor taak 77 veilig uit te proberen — zie de les in
`CLAUDE.md`) en het component
**`HorecagelegenheidEventTabelComponentCopy`**, dat bij een hernoeming
`...Current` werd (controleer of de oude map nog in `lib/` of git staat).
Claude gooit niets weg, dus beide staan er tot jij ze opruimt.
Moet in de **builder** gebeuren, niet met `git rm` — anders staat alles na de
volgende export terug. *9 componenten:* `header_buttons_component_copymethartje`,
`kaart_slider_uitgaan_s_comp`, `kaart_tabel_uitgaan_comp`,
`kaart_tabel_uitgaan_s_comp`, `slider_uitgaan_component_small_current`,
`kanwaeg_select_state_drop_down_component_copy`,
`kanweg_home_uitgaantabel_kaart_component_copy`, `drawer_component_copy`,
`p_uitgaantabel_kaart_component_orgineel_met_kaartjeerin`. *12 pagina's:*
`Event`, `FavorietenCopy`, `HorecagelegenhedenOverzichtCopy`,
`HorecagelegenhedenOverzichtCopy2`, `HorecagelegenhedenOverzichtCopy2Copy`,
`Kanweg`, `KanwegHomeCopy`, `KanwegHorecagelegenhedenOverzichtSortPage`,
`KanwegTestUpload`, `KanwegHorecagelegenhedenOverzicht`,
`KanweghorecagelegenhedenOverzichtCurrentCopy`, `KanweghorecagelegenheidCurrentCopy`.
**Begin met `drawer_component_copy` en `kanweghorecagelegenheid_current_copy`** —
die twee houden `KanwegHorecagelegenhedenOverzicht` als enige nog in leven.
⚠️ `HorecagelegenhedenOverzichtCopy2` pas weggooien als P2-6 af is (het is de
intacte momentopname waaruit het herstel van 09-01 is afgeleid), en de pagina
`Event` weigerde in augustus élke wijziging met "Invalid Action" — lukt die na
één poging niet, laten staan.
- **P1-30 · Cloudflare rate limiting** aanzetten, en dan óók de twee
rate-limiting-vinkjes weghalen uit de custom rule `flutterflow`.
- **P2-25 · Cleanup `custom.module` — debugging alleen op devbob · Eigenaar:
Bob (Drupal-code, aangemaakt 2026-09-14).** Vóór livegang moet de custom
module (`custom.module` + `custom.evenementen_aanmaken.inc` en de overige
`.inc`-bestanden die de `flutterdrup`-Services-resources voeden) goed
werken **zonder overbodig te loggen**. Nu zit er debug-uitvoer in die
tijdens het bouwen van P2-15/de favorieten handig was, maar op productie
alleen de watchdog/dblog volschrijft. Bewust **pas aan het einde** doen,
na de laatste functionele Drupal-wijzigingen — anders raak je de
debug-info kwijt terwijl je 'm nog nodig hebt.
Aanpak:
1. Inventariseer alle `watchdog(...)`/`dpm(...)`/`error_log(...)`/
`drupal_set_message(...)`-debugregels in de custom module
(`grep -n "watchdog\|dpm(\|error_log\|drupal_set_message" custom.module *.inc`).
2. Splits in **echte fouten** (blijven, `WATCHDOG_ERROR`/`WATCHDOG_WARNING`)
en **debug/trace** (request-body dumps, "resource aangeroepen"-regels,
tussenwaarden).
3. Debug/trace achter één schakelaar zetten die alleen op devbob aan staat,
bv. een Drupal-variabele `custom_debug` (`variable_get('custom_debug',
FALSE)`) in een kleine helper `_custom_debug($msg, $vars)`, zodat het
op productie standaard uit staat en op devbob met één
`drush vset custom_debug 1` weer aan kan. Geen debug-code weghalen die
bij een storing op productie nog nuttig kan zijn — alleen uitzetten.
4. ✅ **Nagelopen 2026-09-17 (Bob, grep op productie):** alleen een
uitgecommentarieerde `print_r` (r.791) en een `print_r` binnen
`_custom_debug_watchdog` (r.1948, gaat via de debug-schakelaar). Niets
dat de JSON kan vervuilen. Oud: nalopen dat er geen `print`/`var_dump`/`dd()` meer in staat die de
JSON-respons kan vervuilen (de app parst de body strikt; één regel
ervoor breekt `getJsonField`).
5. Deploy naar productie, Drupal-cache legen, en met een cache-buster
nameten dat elk endpoint nog dezelfde JSON geeft (zie het
`_cb=$RANDOM`-recept in `CLAUDE.md`) én dat de dblog na een testronde
in de app schoon blijft.
Hoort samen met P1-30 (Cloudflare) in dezelfde livegang-ronde.
- **Max items + tab 6** (P2-24 A en C) — geparkeerd, zie daar. Melden bij
FlutterFlow-support is de enige overgebleven route.
**Afgerond 2026-09-11:** taak 18 (route van drawer-item *Horeca* en van de
knop *Alle horecagelegenheden* omgezet naar `horecagelegenhedenOverzichtCurrent`,
beide met de `plaats`-binding op `gemeenteSelectId` opnieuw gezet — die wist
FlutterFlow bij een paginawissel; oude pagina hernoemd en naar `kanweg`
verplaatst) en taak 22 (label + uitlegzin bij het gemeentehartje, allebei
achter de login-guard).
**Afgerond 2026-09-10:** taak 16 (drie titels met onzichtbaar teken —
geverifieerd: "Café Arnhem" staat nu alfabetisch juist), taak 17
(contentfoutjes), P0-13 (login-guards op de hartjes), P1-48 (lege-staat
op de Home-tabs), en de horeca-sortering (alfabetisch, live).
---
---
### P2-46 · EventCurrent: restpunten na de ombouw van 2026-09-19 · Eigenaar: Claude
**Gedaan (export- en toestel-geverifieerd, profile-build op emulator-5554):** backups `EventCurrentCopy`, `EvenementHorecagelegenheidCopy`, `EvenementInfoCopy`; event-kaart met logo 96×96 links en `EvenementInfo` (Expanded) + Maps-knop rechts; de 500 px-Container weg; `EvenementHorecagelegenheid` zonder tabs: `RowLogo` (logo 96×96 + `ColumnRechts`: titel, labels, adres/plaats, telefoon, e-mail), links in een `Wrap` (Menukaart/Website/Facebook/Instagram/X/"Op Uitgaanskrant.com" met host ervoor), `TextInhoud` (zaakbeschrijving), foto's, `ColumnOpening` en `ColumnBezorgen` met Visibility op `openingstijden`/`bezorgtijden`; e-mail leest nu `$[:]['e-mail']`; drie `phone: false`-vlaggen weg; telefoon-guard zonder `valueOrDefault`; Entree-blok alleen bij `entreeprijs`; roze/rode vulkleuren van Entree/Contact weg; EN-vertalingen "Opening hours"/"On Uitgaanskrant.com".
**Nog te doen:**
- Verse profile-build op het toestel voor de láátste ronde (TextInhoud, Menukaart in de Wrap, adres-Row `[Icon, TextAdres, TextPlaats]` met spacing 6): route `uitgaanskrant://uitgaanskrant.com/eventCurrent?nid=214689&horecaid=50013` (A la Ferme, heeft openingstijden) en `horecaid=67233` (Carré, alles leeg).
- `ColumnBezorgen` is alleen in de "verborgen"-tak getest; geen Amsterdamse zaak met `bezorgtijden` gevonden op de eerste 40 van Eetgelegenheden. Zoek er een (`bezorgtijden`/`bestellink` niet null) en bekijk de sectie.
- `TextInhoud` staat onderaan `ColumnInfo` (na het Contact-blok), Body Medium, zonder Max Lines — overweeg padding-top 8 en een kop "Over deze zaak".
- EN-vertaling van de nieuwe literal "Menukaart" (`22xoyrqe`) controleren in `internationalization.dart`.
- Niet getoond: `kvk` (zaak). Bewust overgeslagen.
- ✅ **`organisator` wordt nu wél getoond (2026-09-19, Bob's besluit, af en op toestel geverifieerd).**
Zie het eigen blok hieronder; taak 99 kan hiermee dicht.
- Bob: de drie backup-kopieën (`EventCurrentCopy`, `EvenementHorecagelegenheidCopy`, `EvenementInfoCopy`) op de P2-7-opruimlijst zetten zodra de nieuwe pagina goedgekeurd is. Claude verwijdert niets.
### ✅ 99 / P2-46-deel · `organisator` op `EventCurrent` — AF 2026-09-19 (Claude)
Bob: "organisator wil ik wel getoond hebben bij activiteiten." Gebouwd, geëxporteerd
en op `emulator-5554` (profile) in beide richtingen gecontroleerd.
**Wat er nu staat:**
- `EvenementCall` heeft een JSON-path `$[0].organisator` → getter **`eventOrganisator`**,
type **String** (`castToType<String>`, dus géén `.toString()` — zie de valkuil hieronder).
- `EvenementInfo` heeft een 13e parameter **`organisator`** (String, niet verplicht) en
onderaan zijn Column een `Text` **`TextOrganisator`**: Combine Text `Organisator: ` +
parameter, Body Medium, Visibility *is set and not empty*.
- `EventCurrent` bindt die parameter aan `EvenementCall.eventOrganisator(...)`.
- Export: `if (widget!.organisator != null && widget!.organisator != '') Text('Organisator: ${widget!.organisator}', …)`.
`dart analyze` op een verse export: **0 errors**.
- Toestel: activiteit **nid 214439** toont *"Organisator: Het Team van Klein Frankrijk"*;
evenement **nid 214689** toont de regel niet (en géén letterlijke "null").
**Data-bevinding die je moet kennen vóór je dit test:** `organisator` is op productie
**bijna nooit gevuld** — 2 treffers op 10.000 gescande records van
`flutterflow_events.json` (services_1), en op devbob 0 van 750. De enige twee zijn
**214439** (Brocante Markt Klein Frankrijk, Alblasserdam) en **214418** (Zonta Filmavond,
Deventer); allebei activiteiten zonder `horecagelegenheidnid`. Gebruik die twee als
testnode, anders lijkt het of de regel niet werkt. Het veld zelf is dus gewoon goed
gekoppeld in de view — het wordt alleen zelden ingevuld.
**Afgehandelde restpunten (2026-09-19):**
1. De literal `Organisator: ` zit in een Combine Text en is daardoor **niet vertaalbaar**
(geen i18n-sleutel, niet zichtbaar in het Languages-paneel). Bob: *"maakt niet uit"* —
bewust zo gelaten, geen actie nodig.
2. De custom function `organisatorTekst` was niet meer nodig; **Bob heeft 'm hernoemd naar
`kANWEGorganisatorTekst`** en hij staat op de opruimlijst.
3. ✅ **Voorloopkomma in de adresregel gefixt.** Een activiteit zonder straat toonde
`, Alblasserdam`, want de adres-Row combineerde blind `'${widget!.adres}, ${widget!.plaats}'`.
Nu draait die `Text` op de nieuwe custom function **`adresRegel(adres, plaats)`**
(non-nullable String): lege straat → alleen de plaats, lege plaats → alleen de straat,
allebei gevuld → `straat, plaats`, allebei leeg → lege string. Export:
`functions.adresRegel(widget!.adres, widget!.plaats)` zonder `valueOrDefault`, dus een
leeg resultaat blijft ook echt leeg. `dart analyze` 0 errors; op emulator-5554 (profile)
geverifieerd: nid 214439 toont *Alblasserdam*, nid 214689 toont *Amstel 115-125, Amsterdam*.
**Let op bij testen:** de testwaarde van de variabele `nid` op de API-call `Evenement` staat
sinds deze sessie op **214439** i.p.v. 206292. Dat is bewust (zie de valkuil over
type-inferentie in `CLAUDE.md`) — laat 'm zo staan.
## 🗄 Drupal / views — open
#
### ✅ P1-42 + P1-43 · Verlopen evenementen in de lijsten — AF, nagemeten op productie 2026-09-19 (Claude)
**Alle drie de onderdelen zijn opgelost; er is niets meer te fixen.** Gemeten
met cache-buster op 2026-09-19 02:20:
- `flutterflowmobiel1` services_1/3/4/5/6/7: elk 50 items op pagina 0,
**allemaal vanaf vandaag en oplopend gesorteerd**, nul verlopen items.
(`services_2` geeft 0 items — stond al als ongebruikt bekend.)
- `flutterflowmobiel_establishment_events`: Bimhuis (75459) 14 items, Carré
(67233) 30 — **alleen toekomstig, chronologisch**. De 43%-verlopen-meting uit
de audit van 6 sep geldt niet meer.
- `flutterflowmobiel_establishments`: **alfabetisch** op titel (Afas, Arti et
Amicitiae, Betty Asfalt, Beurs van Berlage, Bimhuis…), niet meer nid-aflopend.
**Het blijvende inzicht — de valkuil bij een volgende viewwijziging:** op
`flutterflowmobiel1` **erven de displays de sortering wél** (geen enkele zet
`defaults['sorts']` uit, dus één wijziging op de Master raakt alle zeven), maar
**zes van de zeven zetten `defaults['filters'] = FALSE` en erven dus géén
filter**. Een criterium op de Master bereikt daardoor maar één display — vandaar
dat de sortering destijds wél aansloeg en het datumfilter niet. Wil je een filter
over alle displays tegelijk: `hook_views_query_alter()` (zoals `datum_van`/
`datum_tot`/`zoek` al doen), niet de Views-UI. En géén *exposed* date_views-filter
gebruiken: dat overleeft PHP 8 niet (500 op een platte string, 0 rijen op de
correcte vorm). Staat ook in `CLAUDE.md`.
Verificatiecommando:
for d in 1 3 4 5 6 7; do printf "services_%s: " $d; curl -s "https://uitgaanskrant.com/nl/flutterdrup/views/flutterflowmobiel1.json?display_id=services_$d&page=0&_cb=$RANDOM" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[0]['datum'],'->',d[-1]['datum'])"; done ```
Oorspronkelijke taakomschrijving + audit van 2026-09-05/06 (historie)
P1-42. De uitgaanslijsten tonen
overwegend verlopen evenementen. Gevonden 2026-09-05 (Claude, live
curl tijdens P2-1-onderzoek), geldt voor flutterflowmobiel1
services_1/2/3 — de views waar Home zijn tabs mee vult.
page=40 levert nog steeds 25
items (eind mei), page=80 idem (half mei). Met infinite scroll aan
scrollt een gebruiker dus de geschiedenis in.created DESC (zichtbaar in Bob's
view-export van flutterflowmobiel_establishment_info, en het gedrag
van flutterflowmobiel1 past daarbij) — dus op wanneer iemand het
evenement invoerde, niet op wanneer het plaatsvindt. Dat verklaart
ook waarom de volgorde binnen een pagina rommelig is (95 van de 149
opeenvolgende paren staan op datum, de rest niet).datum >= vandaag én sorteer
oplopend op de datum-veldwaarde i.p.v. created DESC. Dan staat
het eerstvolgende evenement bovenaan en loopt scrollen de toekomst in.flutterflowmobiel1 filtert >= -2 hours en sorteert oplopend. Dit
punt gaat dus nog alleen over de vraag of dat overal doorwerkt.Audit afgerond 2026-09-06 (P1-43, Claude — live curl, 6 pagina's =
150 items per display, peildatum 6 sep 2026). Het probleem is NIET
beperkt tot services_1/2/3 — het raakt elke evenementenlijst in de app:
| view / display | waar in de app | verlopen | volgorde |
|---|---|---|---|
flutterflowmobiel1 services_1 |
Home-slider | 135/150 (90%) | 97/149 paren oplopend |
flutterflowmobiel1 services_2 |
Home-tabs (alle vijf, zie P1-44) | 142/150 (94%) | 87/149 |
flutterflowmobiel1 services_3 |
Uitgaan-tab + P-pagina's | 143/150 (95%) | 100/149 |
flutterflowmobiel1 services_4 |
Activiteiten-tab | 150/150 (100%) | 75/149 |
flutterflowmobiel1 services_5 |
Cultuur & Info-tab | 142/150 (94%) | 87/149 |
flutterflowmobiel1 services_6 |
Films-tab | 150/150 (100%) | 84/149 |
flutterflowmobiel1 services_7 |
Jeugd-tab | 149/150 (99%) | 89/149 |
flutterflow_events services_1 |
evenement-detail (op nid) | 117/150 (78%) | 102/149 |
flutterflowmobiel_establishment_events |
horeca-detail, agenda van de zaak | 42/97 (43%) | niet chronologisch |
flutterflowmobiel_establishments |
horeca-overzicht | n.v.t. (geen datum) | nid aflopend = nieuwste zaak eerst |
flutterfavorietenagenda |
Favorieten tab 1 | niet gemeten | niet gemeten |
Wat daar per regel bij hoort:
townid erbij wordt het erger, niet beter. services_3 met
townid=25434 (Arnhem) geeft over de volledige paginering 177
evenementen, waarvan 0 toekomstig; oudste 25 oktober 2025. Dat is
deels een inhoudsgat (Willemeen en Theater a/d Rijn hebben in
establishment_events óók geen toekomstige data), maar door
created DESC krijgt de bezoeker wel een pagina die volledig uit
verleden bestaat, zonder enige aanwijzing dat dat zo is.favorieten_agenda.json een 403 met body
["Toegang geweigerd voor gebruiker anonymous"]. drupalRequest maakt
daar (sinds de wijziging van 2026-08-17) een lege [] van, dus de app
crasht niet — maar een verlopen sessie ziet er in de app uit als "je
hebt geen favorieten", zonder melding. Klein los punt, niet dringend.audit/measure.py + run1..8.py); het is 20 regels en zo weer
opgetuigd — jaartal komt uit het /20xx/-segment van het url-veld,
want datum bevat geen jaar.Wat dit betekent voor de ingreep in Drupal: het is één patroon over
alle displays van flutterflowmobiel1 heen, plus
flutterflowmobiel_establishment_events. Zelfde fix (filter
datum >= vandaag, sorteren oplopend op de datumveldwaarde) op alle
zeven displays + de zaak-agenda in één ronde, en apart de vraag of het
horeca-overzicht niet gewoon alfabetisch moet.
✅ Stand 2026-09-19 (verse export, Claude): A is af (alle zes de
StaggeredViews, ook tab 6 Verhuur/catering, draaien op
filterHorecagelegenheden(...) met alleVerhuurCatering), C is af
(.take(25) komt niet meer voor, 6× .take(1000) = dezelfde grens als
maxPaginas), D is af (taak 18) en E is af (0 lege TextField-hints).
Nog open: alleen B. Besluit Bob 2026-09-19: TAB-BEWUST. Eigenaar daarmee Claude.
Bob's nuance: de tab is de hoofdcategorie, de opties zijn de subcategorieën
daaronder. Een zaak kan op twee tabs staan, maar per tab telt alleen de
subcategorie die bij díe hoofdcategorie hoort. Afleiden uit de data van de tab
geeft dus een paar vreemde opties (bv. "Catering" op Eetgelegenheden, 6× in
Amsterdam); netter is de optielijst uit de taxonomie-kinderen van de tab-tid
(horcat 17969/17963/34/17965/17967/17968). Eerst checken of een bestaand
endpoint (categorieen?) die kinderen per tid kan leveren. F (duplicaten) is
op Bob's verzoek geschrapt.
De blokkade-verhalen hieronder zijn daarmee historie; ze staan er nog omdat de
meetmethode nuttig blijft.
🔧 B · Stand 2026-09-19 (bijgesteld): het endpoint bestond al — alleen de access-regel moet om. Mijn eerdere conclusie ("kan niet in de app, nieuw Drupal-endpoint nodig") was te somber; ik keek naar het verkeerde endpoint.
Er zijn TWEE categorie-endpoints, en dat is makkelijk te verwarren:
| Resource | Vocabulary | Waarvoor |
|---|---|---|
categorieen |
vid 10 (field_act_categorie / field_category2) |
Stadsactiviteit + Uitgaansevenement |
horecacategorieen |
vid 6 (field_categories, horecagelegenheid_category) |
Horecagelegenheid — dit is degene die we nodig hebben |
Dat de horeca-tabs in vid 6 zitten blijkt uit custom_form_alter() in
custom.module: taxonomy_get_tree(6, 34) plus
array(1 => 17969, 2 => 17963, 3 => 34, 4 => 17967, 5 => 17968) — exact de zes
horcat-waarden.
_custom_horecacategorieen_service() geeft al de juiste vorm terug — het
loopt via _custom_term_reference_field_options(), en die levert per term
tid, naam, diepte én parent_tid. Er hoeft dus niets aan die functie
te gebeuren.
Enige blokkade: access callback => user_is_logged_in. Het horeca-overzicht
is een publieke pagina. → Bob, zie zijn taak hierboven.
Waarom het niet zonder dit endpoint kan: het veld categorie van
flutterflowmobiel_establishments bevat alle labels van een zaak, over al zijn
hoofdcategorieën heen. Tab Eetgelegenheden levert zo 57 verschillende labels op
met o.a. "Catering" (26×) en "Zaalverhuur"; tab Cultuur geeft "Cafe" (11×). Een
zaak die op twee tabs staat is uit die respons principieel niet toe te wijzen aan
de juiste hoofdcategorie. Met parent_tid uit horecacategorieen is dat één
filterregel.
✅ Gemeten op devbob 2026-09-19 (Bob heeft de access callback omgezet): HTTP 200 anoniem, 129 records, 8,8 KB, 0,22 s. Exact 6 termen op diepte 0 — de zes tab-tids — en 123 op diepte 1. Dieper dan 1 gaat de boom niet, dus "directe kinderen van de tab-tid" is precies de goede filterregel.
| Tab | horcat |
naam in de boom | subcategorieën |
|---|---|---|---|
| Activiteiten | 17969 | Activiteit | 26 |
| Cultuur | 17963 | Cultuur & Info | 15 |
| Eetgelegenheden | 34 | Eetgelegenheden | 52 |
| Overnachten | 17965 | Overnachten | 4 |
| Uitgaan | 17967 | Uitgaansgelegenheid | 19 |
| Verhuur/catering | 17968 | Verhuur, Verkoop & Catering | 7 |
⚠️ De naam van de wortelterm wijkt soms af van het tablabel (Uitgaan vs Uitgaansgelegenheid). Onschadelijk — er wordt op tid gefilterd, niet op naam.
horcatSubTids/horcatSubLabels zijn tegen deze echte respons nagerekend: per tab
even lang, geen lege labels, geen duplicaten, en alle randgevallen (respons null
of leeg, horcatTid null/leeg, onbekend tid) geven netjes een lege lijst.
✅ Productie 2026-09-19: horecacategorieen.json is publiek (HTTP 200
anoniem, 129 records, 8,8 KB, 0,22 s), identiek aan devbob.
✅ Cloudflare staat erop (Bob, 2026-09-19): gemeten MISS → HIT → HIT.
⚠️ Kleine kanttekening op die regel: de voorwaarden method eq GET en
not http.cookie contains "SESS" staan alleen in de eerste haakjesgroep
(plaatsen_bij_gemeente.json); plaatsen.json en horecacategorieen.json
hangen er als losse or-groepen achter en worden dus zonder die twee
voorwaarden gecachet. Voor deze drie is dat ongevaarlijk — het zijn
taxonomie-endpoints zonder gebruikersafhankelijk antwoord — maar trek de
voorwaarden er bij gelegenheid overheen, anders lekt een toekomstig
gebruikersafhankelijk endpoint stil in een gedeelde cache.
✅ Al gebouwd en export-geverifieerd (Claude, 2026-09-19), 0 analyze-errors:
Horecacategorieen — top-level, absolute URL, cache: true,
geen headers.horcatSubTids / horcatSubLabels (per
horcat-tid) en horcatOptiesVoorTab / horcatLabelsVoorTab (per
tabindex; eerste element '' resp. Alle categorieen). De tabvolgorde
zit erin als ['17969','17963','34','17965','17967','17968'].horecagelegenhedenOverzichtCurrent, als 2e kind van de
body-Column (tussen zoekveld en tabs), mét Backend Query → API Call →
Horecacategorieen.De filterkant was al klaar: filterHorecagelegenheden heeft al een
categorieFilter-parameter en alle zes de tabs lezen de gedeelde page state
categorieFilterActiviteiten — die wordt alleen nergens gezet.
⚠️ Die filter vergelijkt op labels (de categorie-array van een zaak), niet
op tids; daarom leveren mijn functies labels als dropdown-waarde.
✅ Alle vier de resterende stappen zijn AF (Claude, 2026-09-19, export-geverifieerd):
horcatOptiesVoorTab(respons, tabIndex) —
respons = Horecacategorieen Response → JSON Body → No Further Changes,
tabIndex = Widget State → TabBar Current Index (die binding zette Bob).horcatLabelsVoorTab
met dezelfde twee argumenten.categorieFilterActiviteiten = Widget State → DropDown, Rebuild Current Page.
Export: _model.categorieFilterActiviteiten = _model.dropDownValue;categorieFilterActiviteiten → Reset Value, en Reset Form Fields →
DropDown_1, Rebuild. Export: categorieFilterActiviteiten = null +
dropDownValueController?.reset().double.infinity
(gelijk aan het zoekveld erboven).⚠️ Twee valkuilen die hierbij tijd kostten (ook naar CLAUDE.md):
Option 1/2/3. Zet daarna dus
beide opnieuw.Status: 400). Kom je er niet uit:
Remove vóór je de pagina verlaat.✅ Op toestel getest (profile-build, emulator-5554, Amsterdam 28695, 2026-09-19):
Restpunten (klein):
horcatLabelsVoorTab geeft de eerste optie als "Alle categorieen" (zonder
trema) — cosmetisch, alleen in de dropdownlijst zelf; de hint heeft wél "ë".6mmrjr12) is leeg → mee in P1-17.Bekende datakwestie, geen blokkade: een tab toont álle subcategorieën uit de taxonomie, ook die in de gekozen plaats geen enkele zaak hebben (Amsterdam: Eetgelegenheden 52 opties waarvan 38 met resultaat; Activiteiten 26 waarvan 6). Aanscherpen kan later door de optielijst te snijden met de labels die in de geladen lijst voorkomen.
✅ Kommaprobleem opgelost (Bob, 2026-09-19): tid 17968 is hernoemd van "Verhuur, Verkoop & Catering" naar "Verhuur & Catering". Nagemeten: geen enkele term in de boom bevat nog een komma, en in de zakenlijst van die tab (42 zaken, Amsterdam) staat géén label meer dat niet in de taxonomie voorkomt.
App-deel (Claude), zodra het endpoint publiek is:
horcatSubTids en horcatSubLabels staan in het
project (2026-09-19, export-geverifieerd, dart analyze 0 errors).horecagelegenhedenOverzichtCurrent, gevoed uit één
horecacategorieen-call (cache: true).filterHorecagelegenheden(...) — zelfde route als het
zoekveld al gebruikt.⚠️ Verifieer als eerste dat de zes tab-tids (34/17963/17965/17967/17968/17969)
op diepte: 0 in de respons staan — dat bevestigt dat field_categories op vid 6
zit. Zo niet, dan wijst field_categories naar een andere vocabulary en moet de
service een ander veld introspecteren.
P2-24.
Pagina: horecagelegenhedenOverzichtCurrent. P2-6 heeft het zoekveld op 5
van de 6 tabs werkend en live op een toestel bevestigd (zie het
✅-blok bij P2-6). Dit zijn de punten die nog over zijn; ze hebben allemaal
Bob nodig — óf omdat Claude er aantoonbaar niet doorheen komt, óf omdat er een
keuze in zit. Het werkende recept per StaggeredView en alle valkuilen
staan bij P2-6; niet opnieuw uitzoeken.
⛔ A en C zijn geblokkeerd door één en dezelfde oorzaak — lees dit eerst (2026-09-09, Claude, hard gemeten).
Het Generate Dynamic Children-paneel schrijft op deze pagina niets meer weg. Dat is een nieuwe, veel bruikbaardere diagnose dan het oude verhaal ("de bron Custom Functions rendert zijn optielijst niet") — dat was een symptoom, niet de oorzaak. Het bewijs:
1000. Een verse export toonde in beide
gevallen onveranderd .take(25), zes keer. Leeg én een getal komen dus
allebei niet door — het ligt niet aan een lege waarde.Variable Name-veld, én via een selectiewissel in de widget tree: alle
drie geen verschil.TextFields (punt E) kwam op dezelfde dag wél gewoon door, net als de
hernummering van textController1 → textController die FlutterFlow
daarbij zelf doorvoerde.Gevolg: zowel punt A (tab 6 koppelen) als punt C (Max Items weghalen)
lopen via dit paneel en zijn daarmee niet uitvoerbaar. Bob heeft A twee keer
geprobeerd, Claude ~15 keer plus zes keer op Max Items; het bestand kwam elke
keer byte-identiek terug uit de export.
Duplicaat-test GEDAAN 2026-09-10 (Bob's akkoord) — en die werkt NIET.
Duplicate Page op Copy3 gaf horecagelegenhedenOverzichtCurrentCopy.
Daar meteen dezelfde ingreep geprobeerd: Max Items van 25 naar leeg op de
eerste StaggeredView; de UI toonde weer netjes "Leave empty for no
limit…". Verse export: 6× .take(25), precies als in het origineel.
De blokkade zit dus niet in de opgeslagen pagina-data — een verse
kopie erft 'm gewoon. Daarmee vervalt de beste hypothese en is er geen
route meer die Claude of Bob in de builder kan proberen.
⚠️ Opruimen: horecagelegenhedenOverzichtCurrentCopy moet weg (Bob —
Claude verwijdert niets). De pagina heeft verder niets gedaan en wordt
nergens naartoe genavigeerd; hij bestaat alleen als restant van deze test.
Toevoegen aan P2-7.
Wat dan wel: Bob's terugvaloptie van 2026-09-10 — "laten we dat probleem, max items, even voor wat het is; moeten we dat een punt maken voor de livegang." Dus A en C blijven open als livegang-punt. De enige overgebleven route is een melding bij FlutterFlow-support, want dit is aantoonbaar een bug aan hun kant: hetzelfde paneel accepteerde deze wijzigingen in september nog wél, en een ander paneel op precies dezelfde widget (Empty List Widget, zie P1-46) schrijft gewoon weg.
A. Tab 6 (Verhuur, catering) alsnog koppelen. De enige StaggeredView die
nog op de rauwe API-respons staat; alle andere vijf draaien op
filterHorecagelegenheden(...). Recept staat bij P2-6, met
items = alleVerhuurCatering.
⚠️ Claude komt hier niet doorheen — twee sessies, ~15 pogingen: in de
Set-Variable-dialoog van uitgerekend deze ene StaggeredView rendert de bron
Custom Functions zijn optielijst nooit. Uitklappen lukt (chevron slaat om),
maar de rij filterHorecagelegenheden eronder verschijnt niet — niet na
hoveren, niet na blind klikken op de verwachte positie, niet na de dialoog te
sluiten en te heropenen, en niet na een volledige herlaad van de builder. Op
de andere vijf tabs werkte exact dezelfde reeks wél. Niets kapot: de tab staat
gewoon nog op zijn oorspronkelijke binding en werkt zoals voorheen.
B. Categoriedropdown (was P2-6 stap 4) — eerst een ontwerpkeuze van Bob,
daarna kan Claude bouwen. Er is een custom function nodig die de unieke
categorie-waarden uit de dataset haalt. Claude heeft die bewust nog NIET
aangemaakt: er zit een productkeuze in, en een eenmaal aangemaakte custom
function kan Claude niet meer verwijderen (staande regel). Twee varianten:
alleXxx-lijsten +
TabBar Current Index (Widget State) + het pad = 8 argumenten. De
dropdown toont precies de categorieën van de zichtbare tab. Nadeel: 8
bindingen in juist die dialoog die bij punt A vastliep.Column is bewezen werkend, net als bij het zoekveld).
⚠️ categorie is in de API een LIJST, geen string — bevestigd 2026-09-05
met curl op Arnhem (townid=25434): "categorie": ["Bioscoop"]. De bestaande
filterHorecagelegenheden gaat daar al goed mee om, maar de nieuwe
unieke-categorieën-functie moet de binnenlijst plat slaan en niet
.toString() op het hele veld doen — anders krijg je opties als [Bioscoop]
die nooit matchen. Aantallen in Arnhem: Activiteiten 2 items / 2 categorieën,
Eetgelegenheden 20 / 19, Uitgaan 4 / 4.C. Max Items staat per tab op 25 — Bob's besluit 2026-09-09: weghalen.
⛔ Geblokkeerd, zie het kader hierboven. Dit is een ECHTE blocker, geen
cosmetiek. Gemeten op productie in Amsterdam (townid=28695):
| tab | items | zichtbaar met take(25) |
|---|---|---|
| Eetgelegenheden | 245 | 25 |
| Uitgaan | 75 | 25 |
| Activiteiten | 42 | 25 |
| Overnachten | 42 | 25 |
| Verhuur, catering | 24 | 24 |
| Cultuur | 14 | 14 |
⚠️ CORRECTIE 2026-09-10 op een eerdere bewering hier: het zoekveld
doorzoekt WÉL de volledige lijst. De gegenereerde code is
filterHorecagelegenheden(<volledige lijst>, zoekterm, …).toList().take(25).toList()
— dus eerst filteren over alle 245, dán afkappen. Zoek je "pizza" in
Amsterdam, dan worden alle 245 doorzocht en zie je de eerste 25 treffers.
De eerdere formulering "het zoekveld doorzoekt alleen die 25" was fout.
Wat er wél overblijft: zonder zoekterm zie je 25 van de 245 en kun je
niet doorbladeren, want deze pagina heeft geen pager — geverifieerd: 0
PagedMasonryGridView, 0 PagingController, 0 infinite scroll, zes
gewone MasonryGridView. De infinite scroll is er bij P2-6 bewust
uitgehaald omdat client-side filteren de volledige dataset nodig heeft.
In Amsterdam/Eetgelegenheden zijn 220 zaken dus alleen via het zoekveld
bereikbaar, niet door te scrollen.
(Een eerdere inschatting "je merkt er weinig van" was gebaseerd op Arnhem,
grootste tab 20 items. Te klein om iets over limieten te zeggen; gebruik
voortaan Amsterdam.)
Haalbare verzachting zolang take(25) vastzit — voorstel voor Bob:
sorteer de lijst in fetchAlleHorecagelegenheden (een custom action,
en de Custom Code-editor werkt gewoon). Nu is de volgorde die van de view
(nid aflopend), dus je krijgt 25 min of meer willekeurige zaken.
Alfabetisch gesorteerd krijg je 25 voorspelbare, en samen met het zoekveld
is dat werkbaar. Dit dekt meteen de openstaande wens "Horeca-overzicht
sorteren" (zie de wachtrij bovenaan) zónder dat de Drupal-view aangepast
hoeft te worden.
Weghalen is veilig zodra het kan: er is al een harde begrenzing elders
(fetchAlleHorecagelegenheden loopt door API-pagina's van 100 met
maxPaginas = 10, dus max 1000 per tab), en alle zes de grids zijn
MasonryGridView.builder — lazy, dus alleen zichtbare kaarten worden
gebouwd. Er is geen paginering/infinite scroll meer op deze pagina (0
PagingControllers); dat is bewust, want client-side filteren kan alleen met
de volledige dataset. Wil je ooit strakker begrenzen, doe dat in
maxPaginas, niet in de weergavelimiet.
D. Route omzetten (was P2-6 stap 7) — bewust uitgesteld tot A en B klaar
zijn. Twee Navigate To-wijzigingen naar horecagelegenhedenOverzichtCurrent:
lib/shared/drawer_component/drawer_component_widget.dart en
lib/horecagelegenhedenoverzicht/horecagelegenheid_current/horecagelegenheid_current_widget.dart.
Daarna wordt de oude HorecagelegenhedenOverzicht een orphan → toevoegen aan
P2-7. Ook Copy3 heet dan nog "Copy3" terwijl het de levende pagina is;
hernoemen of niet is Bob's keuze.
E. Twee lege TextField-placeholders opruimen. In tab 0 (Activiteiten) en
tab 1 (Cultuur) staat nog het oude, ongebonden TextField met hint letterlijk
"TextField". Op een toestel is dat een zwevend wit vak dat half over de
TabBar valt — lelijk en verwarrend naast het echte zoekveld. De eerdere
afspraak "laten staan tot P2-6 ze van een echte binding voorziet" is hiermee
afgehandeld: het echte zoekveld staat nu bóven de TabBar, dus deze twee zijn
overbodig. Claude verwijdert niets.
Niet oplosbaar, geaccepteerd: FlutterFlow zet op de On Change-trigger
zelf een EasyDebounce van 2000 ms en die is nergens instelbaar (geen
debounce-eigenschap op het widget, en de trigger-/actiemenu's bieden 'm
niet). Live merkbaar: de lijst ververst ~2 s nadat je stopt met typen. Werkt
correct, voelt traag.
Nagemeten door Claude op productie, 2026-09-19 17:44 (alle zeven displays
volledig gepagineerd, nids naast flutterflow_events gelegd, geclassificeerd op
datum + tijd tegen de >= -2 uur-grens van de master):
| echte toekomstige events | 6.684 |
| bereiken geen enkele tab | 618 (9 %) |
| ... daarvan zonder categorie | 618 — dus 100 % |
| ... daarvan mét categorie | 0 |
Twee dingen die dit uitwijst. (1) De zeven displays dekken alle categorieën correct af — er is geen categorie meer die nergens landt, dus dát deel van taak 28 is klaar. (2) Het hele resterende lek is één oorzaak: een event zonder categorie valt door elk display-filter heen en is in de app dus onvindbaar. Stand 12 sep: 597. Nu: 618. Het loopt dus licht op — de fix van 19 sep werkt kennelijk niet (of alleen voorwaarts, terwijl de bestaande nodes blijven staan).
Voorbeelden (allemaal ruim in de toekomst, dus geen datumkwestie):
222806 Steve 'n' Seagulls (20 dec, Weert) · 222737 Altruism Label Night
(21 nov, Maastricht) · 222703 BRECHTJE Maresa (1 okt, Groningen).
Voorstel — een default in custom_node_presave(). Die hook draait er al
voor go_out_event (adres/geo/plaats overnemen van de horecagelegenheid); één
blok erbij dat field_category2 vult met een vaste fallback-tid zodra het leeg
is, dekt alle nieuwe imports. De 618 bestaande haal je in één keer mee met een
drush php-eval die diezelfde tid zet en node_save() aanroept.
Wat Claude van Bob nodig heeft: welke tid de fallback moet zijn (een
bestaande categorie, of een nieuwe term "Overig"). De alternatieven — een achtste
display zonder categoriefilter, of de importer aanpassen — kosten meer en geven
een extra tab in de app.
⚠️ Meetles, kostte deze sessie een valse ronde: classificeer op datetime,
niet op datum. Op datum alleen leverde dezelfde meting 30 "bugs" op die in
werkelijkheid gewoon events van vandaag waren die al begonnen waren — precies wat
het >= -2 uur-filter hoort weg te laten. Staat ook al in CLAUDE.md; ik trapte
er alsnog in.
#
launchURL-aanroepen in
levende code hangen achter een guard op de rauwe waarde, niet op een
valueOrDefault.EstablishmentInfo geeft precies één rij per zaak (60 Amsterdamse zaken
getest). De zes $[:].veld-knoppen op de detailpagina leunen daarop; let op
zodra er een multi-value veld aan die view wordt toegevoegd.categorie is op alle zeven flutterflowmobiel1-
displays altijd een lijst (65 records gemeten).SliderUitgaanComponentSmallCurrent negeert zijn eigen
townid/displayid (P1-44-patroon), maar heeft nul gebruikers — geen
impact, alleen niet inzetten zonder dat eerst te repareren.fillColor op de twee aanmaakpagina's is al goed: zowel
stadsactiviteitAanmaken (18 widgets) als uitgaansevenementAanmaken (11)
staat volledig op primaryBackground. Het in CLAUDE.md voorspelde
secondaryBackground-defaultprobleem is dus niet meer aanwezig.$.categorie is op de Home-displays nooit leeg of afwezig (services_1/3/5,
Amsterdam, 25 items elk: altijd een lijst). De .toList() zonder true op
home_uitgaantabel_kaart_component_widget.dart:651 kan daardoor in de praktijk
niet crashen — events zonder categorie halen de displays sowieso niet.EvenementInfo (6 van de 12,
nagemeten en juist), de wees hierboven, en
HorecagelegenheidoverzichtKaart.nid (restant van het verwijderde hartje).node.title, een spatie komt
vóór de A). De eerste kaarten die een gebruiker ziet zijn dus willekeurig.android/ heeft geen ongecommitte
wijzigingen meer en de vastgelegde waarden zijn minSdkVersion
flutter.minSdkVersion (= 24) en tasks.register("clean", Delete) — precies
de moderne variant. Het punt is dus vanzelf beslecht; niemand hoeft hier nog
iets aan te doen.flutterflowmobiel1 opruimen (was taak 46): GESCHRAPT op Bob's verzoek.
services_2 bestaat nog (25 items) maar heet "ongebruikt" en blijft staan
tot een schoonmaakronde.| 09-13 | 09-14 | |
|---|---|---|
unieke events (flutterflow_events) |
9625 | 16.065 (18.207 rijen) |
| toekomstige events | 38 | 6.202 |
| laatste datum in voorraad | 25 sep | 10 jan 2027 |
| per maand | — | sep 1487 · okt 2297 · nov 1412 · dec 894 · jan 112 |
Het contentgat is dicht — de import levert nu maanden vooruit. De zeven
flutterflowmobiel1-displays zijn schoon: 0–5 verlopen items per display op
duizenden rijen (Uitgaan 3275, Cultuur 4654, Films 702, Jeugd 535,
Activiteiten 100). P1-42 (lijsten vol verlopen events) is daarmee feitelijk
opgelost.
Wat nog niet in de app komt: 622 van de 6.202 komende events (10%).
HTML-entiteiten (taak 29): titels en adressen zijn nu schoon (0 van 6.202
komende events). body heeft er nog 2.570, maar die gaat in de app door de
custom widget FromHTML en rendert dus goed. Enige rest: 55 records met een
entiteit in categorie (Hiphop/Rap/R&B, DJ's) — die staan
letterlijk in het groene label. Kleine Drupal-fix op het categorie-veld van
flutterflow_events.
Duplicaten (taak 30): 2.095 nids komen meer dan één keer terug (2.142 overtollige rijen) — onveranderd, en nog steeds onzichtbaar in de app.
Meetscript: fetch.py + analyse.py (Nederlands én Engels datumformaat,
jaar uit de weekdag) — stond in de scratchpad van deze sessie; recept staat in
CLAUDE.md.
#
flutterflow_events — bekend, geen impactNid 214439 ("Brocante Markt Klein Frankrijk") komt 14 keer identiek terug
in flutterflow_events; 214441 en 214420 elk 3x, 214367 2x. Op 2026-09-12
onveranderd (18 overtollige rijen op 2025).
✅ Maar het bereikt de app niet. Nagemeten 2026-09-12:
flutterflowmobiel1 heeft géén duplicaten: services_1/2/3/5/7 geven
allemaal evenveel rijen als unieke nids. Node 214439 komt er zelfs
helemaal niet in voor (zijn categorie "Tweedehands markt" valt onder geen
enkele display — dat is taak 28, niet deze).flutterflow_events wordt door de app alléén per nid bevraagd
(EvenementCall.call(nid: ...), vanuit evenement_component en
event_current). Er wordt nergens een lijst uit opgebouwd: de twee
iteraties in dat component lopen over fotoos en categories binnen één
event, niet over de rijen.Gevolg: geen zichtbare schade, geen haast. Het blijft wel iets om te weten:
bouwt er ooit iemand een lijst op deze view, dan komt zo'n node 14x in beeld.
De oorzaak is het klassieke Views-patroon "een veld met meerdere waarden krijgt
een eigen rij" — kijk op node 214439 welk veld 14 waarden heeft en zet
Multiple field settings → Display all values in the same row aan.