*Opgeschoond 2026-09-19 (van 1924 naar ~800 regels). Afgeronde taken zijn
verwijderd, niet gearchiveerd — ze staan in de git-historie en de blijvende
inzichten 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", 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.
De app-kant is vrijwel af. Op 2026-09-19 zijn o.a. afgerond: de
header-locatie-keten (Home toont Heel Nederland, provinciepagina de
provincienaam), het adaptive icon, de loginpagina, het drawer-logo, Crashlytics,
"Mijn aanmeldingen", de categoriedropdown op het horeca-overzicht, de
horeca-agenda-entiteiten en de opruimronde. Op 2026-09-20 kwamen daar het dichtgezette API-oppervlak (42 → 22 methodes,
het name-lek weg) en de feature graphic bij. Wat rest is livegang-werk:
Google Play en één doorloop die alleen Bob kan doen.
| # | Taak | Kort |
|---|---|---|
| FOTOBOEK | Fotoboek: foto's kiezen uit Drupal + beheren | Claude — bezig (sessie 2026-09-22). Drupal-kant AF (endpoint mijn_fotos index+verwijder, live op devbob én productie, getest). App-kant: eerste plek werkend — logo op uitgaansevenementAanmaken. API Call MijnFotos (Predefined Path items, cookie-header), page states fotoboekOpen/fotoboekDoel, knop Uit fotoboek (toggle) naast Logo kiezen, en een GridView (3 kolommen, $.thumb) waarvan een tik createLogoFid=$.fid + createLogoUrl=$.url zet en het blok sluit. dart analyze 0 errors. Rest: omzetten naar herbruikbaar component en uitrollen naar de 3 andere upload-plekken (foto's hier + logo/foto's op stadsactiviteitAanmaken), Camera/Galerij-knoppen in hetzelfde blok, 5e tab "Mijn foto's" op mijnProfiel voor beheer/verwijderen (custom function fotoVerwijderBody), lege staat via LegeLijstComponent + vertalingen. Volledig plan + endpoint-spec in CLAUDE.md (commit 8c73621) |
| 133 | Kopie-knop verkleinen (mijnProfiel 2x + MijnAanmeldingen) | Claude — bezig (plan af 2026-09-22, wacht op groen licht voor de browser). Variant A (icoon in de rij) op mijnProfiel, variant B (compacte chip) op MijnAanmeldingen, tooltip erbij, kleuren meteen goed. Volledig plan + terugvalopties onderaan dit bestand |
| G0.6 | Play-screenshots | ✅ Nieuwe set van 10 klaar (2026-09-19 avond). Alleen tel-04-horeca.png moet over zodra Show Test Ads uit staat |
| 112 | Restje zoek-stad: lege API-call opruimen | Eigenaar: Bob. SelectStateDropDownComponent → Actions → On Page Load → Conditional Action 5 (gemeenteSelectId is set/non-empty) → TRUE-tak → Action 13, Custom Action drupalRequest met url/sessionName/sessionId/token/body allemaal Unset. Staat sinds 2026-09-20 op disabled; kan weg via ⋮ → Delete Action. Vlak eronder staat Conditional Action 16 (apiGemeenteResultInitalize -> Succeeded) met twee lege takken — mag in dezelfde moeite weg |
| 114 | Opruimen na de filterbalk-ombouw | Eigenaar: Bob. Restjes van taak 113 (afgerond 2026-09-20): (a) component-parameter parameter1 op FilterBalkComponent ontstond automatisch bij Convert to Component en wordt nergens gelezen — mag weg, en daarmee ook de meegegeven waarde op Home en PUitgaanPage; (b) page state zoekOpen op Home is overbodig (de App State-versie stuurt alles); (c) App State zoekActief (Boolean) bestond al vóór deze taak en heeft 0 gebruikers; (d) wegwerp: componenten FilterBalkKanweg en FilterBalkComponentCopy + pagina's homeCopy en PUitgaanPageCopy. Geen van alle blokkeert iets — dart analyze geeft 0 errors |
| 128-B t/m F | Nieuwsbrieven-tab op mijnProfiel — Claude, bezig (2026-09-21) | Backend staat live en is getest; klaar om te bouwen zodra de lopende FlutterFlow-sessie klaar is (Bob meldt dat). Vierde tab + waarschuwing bij 0 gevolgde gemeenten + verwijzing vanaf Favorieten + vertalingen (teksten staan klaar). ⚠️ CSRF-token meegeven, zie het blok hieronder |
| # | Taak | Waarom nu |
|---|---|---|
| G | Google Play-checklist G0–G4 | Volledige checklist hieronder |
| 65 | De ingelogde doorloop | Kernfuncties zijn nooit op een release-build getest. Claude kan dit niet (geen wachtwoorden) |
| AdMob | Show Test Ads uit |
App Settings → AdMob. Wacht op de Google-registratie |
| P1-17 | Vertalingen | ✅ Rond voor de app (2026-09-20): 0 van de 218 gerenderde sleutels mist nog Engels. Alleen de vier lege-lijst-teksten op mijnProfiel blijven over — dat zijn parameterwaarden, geen i18n-sleutels |
| Drupal | taak 56-restant · taak 28/109 | Cron-description op "xx days" ✅ gedaan · events zonder categorie gaat via de importer |
| Bij livegang | Schakelaars en opruimklussen | Eigen blok hieronder |
| ✅ AF (Bob 2026-09-20, export-geverifieerd): volgorde is nu slider → banner → filterbalk → lijst | ||
✅ AF (Bob 2026-09-20): Container 65/65/105/105 mét de padding erin, dus de banner krijgt zijn volle 50 resp. 90 dp én de lucht tot de slider blijft |
||
| 121 | 15 verweesde mappen in lib/ opruimen (+ get_streamed_response_web.dart/_http.dart, zie 2026-09-21) |
Staan lokaal én in git, maar FlutterFlow levert ze niet meer. Twee ervan breken dart analyze. Eigen blok hieronder |
| 120 | 12 hardcoded kleuren aan thematokens binden | ⚠️ Claude kwam er 2026-09-20 niet in: widget-selectie in de tree landt niet in Bob's huidige Chrome-venster. Inventaris mét vindplaatsen staat klaar |
✅ AF (Bob 2026-09-20, export-geverifieerd): READ_CALENDAR/WRITE_CALENDAR én NSCalendarsUsageDescription zijn weg; CAMERA staat er nog (terecht) |
||
| 118 | AdMob EU-consentbericht | ⚠️ Half: de FlutterFlow-toggle staat goed, maar het bericht moet in de AdMob-console gepubliceerd zijn. Eigen blok hieronder |
| 122 | Teksten aanmaakpagina's | ✅ A/B/C af (Claude 2026-09-20, 19 teksten, export-geverifieerd): verkeerde woorden, typefouten, technische veldnamen en het sterretje bij de categoriekeuze. Open blijven D (hints die het label herhalen), E-restje en F (Select... op een NL-pagina) — eigen blok hieronder |
| 123 | mijn_stadsrechten uitbreiden met de keten |
Code staat klaar en is op productie getest. Vervang regels 316-372 van custom.evenementen_aanmaken.inc + cc all. Blokkeert 124 |
| 124-126 | Stadsrechten-cascade op stadsactiviteitAanmaken |
Voorvullen van provincie/gemeente/plaats (124), een crash bij een lege plaatsenlijst (125) en een stille verkeerde plaats bij het wisselen van gemeente (126). Eigen blok hieronder |
| 128-A | ✅ KLAAR — nieuwsbrieven-resource staat live | Gedeployd en getest op productie 2026-09-21. Rest: één tikfout in de omschrijving van tid 36676 (verstuurt → verstuurd), zie het blok hieronder |
| 129 | Sessie-verlopen-melding afmaken/verifiëren | Niet in deze chat (Bob 2026-09-21). De afhandeling bestaat: drupalRequest herkent 401 en 403-met-"anonymous", zet sessieVerlopen en VerbindingsBanner hangt in HeaderButtonsComponent. Staat alleen in de builder, niet in de gecommitte lib/ — dus nooit geëxporteerd/gecommit. Nalopen of het af is en of de banner ook echt verschijnt. Eigen blok hieronder |
| 130 | Inloggen met Facebook/Google (OneAll) | Wens Bob 2026-09-21. Onderzocht en akkoord: OneAll-abonnement gaat naar Personal Advanced ($27/mnd jaarlijks) voor Direct Connect. Geen Play-blocker, maar wél de privacyverklaring/Data Safety als het meegaat in de eerste release. Volledig stappenplan in een eigen blok onderaan |
#
(P2-45b, P2-45c en 76 zijn op 2026-09-19 afgerond en geverifieerd met een verse export — zie de commit van die datum.)
⬜ De vier lege-lijst-teksten op mijnProfiel hebben geen Engelse
vertaling. Een component-parameterwaarde is een letterlijke string, geen
i18n-sleutel, dus er is geen globe. Bewuste keuze (2026-09-20); meepakken bij
de bredere vertaalronde (P1-17), niet los.
⬜ MijnAanmeldingen: de "Gepubliceerd"-tak is nooit met echte data gezien.
Alle testaanmeldingen op devbob staan op status: 0, dus de lijst toont
uitsluitend "Wacht op goedkeuring". De tekst komt kant-en-klaar uit Drupal
(status_label, een ternary op (int) $node->status === 1), dus het risico is
klein — één testnode publiceren en de lijst openen volstaat om dit af te
vinken.
⬜ Optioneel: ingang naar MijnAanmeldingen op Favorieten tab 4. Nu loopt
de enige route via MijnProfiel → Bekijk alles. Dat volstaat; alleen
oppakken als je 'm ook direct vanaf Favorieten wilt kunnen bereiken.
Open rest — vier punten, alle vier klein:
favorieteGemeenteIds loopt achter na een verse
install, waardoor de waarschuwing ten onrechte kan verschijnen. Twee
uitgezochte fixroutes staan bij 128-C.mijnProfiel voor Initial Tab Index).status == 2-regel uit 128-F (één conditionele Text); komt met
custom_nieuwsbrieven_confirm = FALSE nu nooit voor.Wens Bob 2026-09-21. Op de website kan een ingelogde gebruiker op
/user/<uid>/simplenews drie nieuwsbrieven aan- en uitvinken. Dat moet ook in
de app kunnen. Plan is af en volledig doorgemeten (2026-09-21); alleen A moet
gebouwd/gedeployd worden vóór B t/m D kunnen.
Simplenews 7.x-1.1, nieuwsbrieven zijn taxonomy-termen in vocabulary
newsletter (vid 11). ⚠️ variable_get('simplenews_vid') geeft 0 — leid de
vocabulary dus nooit daaruit af, gebruik simplenews_category_get_visible().
| tid | Nieuwsbrief | actief | onbevestigd | uitgeschreven |
|---|---|---|---|---|
| 18017 | Ondernemers nieuwsbrief | 11 | 360 | 0 |
| 36667 | Uitgaanskrant voor bezoekers van de horeca | 16 | 47 | 1 |
| 36676 | Uitgaanskrant.com wekelijkse uitgaansagenda | 18 | 43 | 2 |
Opslag: simplenews_subscriber (snid/mail/uid/activated) +
simplenews_subscription (snid/tid/status/source). Status: 1 = actief,
0 = uitgeschreven, 2 = onbevestigd.
De wekelijkse agenda (36676) hangt al aan de app. De edities heten
"Favoriete Gemeenten" en selecteren content via de flag favorite_town
(fid 2) — precies de flag die "Gemeente volgen" in
SelectStateDropDownComponent al zet via favorieten/flag. Er hoeft dus niets
gekoppeld te worden; alleen het abonnement zelf ontbreekt.
simplenews_subscribe_user($mail, $tid, $confirm, $source) heeft twee takken
(simplenews.module:1281):
$confirm = FALSE → status meteen 1 (actief), géén mail, alleen
module_invoke_all('simplenews_subscribe_user', ...). Dit is wat de
user-settings-pagina uit het screenshot doet
(simplenews.subscription.inc:97, source 'website').$confirm = TRUE → status 2 (onbevestigd) + bevestigingsmail met
link; pas ná het klikken wordt het actief. Hier komen die 360 onbevestigde
inschrijvingen vandaan.Een welkomstmail bestaat niet: simplenews_rules levert alleen het event,
en geen van de 6 Rules op de site luistert ernaar (alle zes hangen aan
node_insert/user_insert/node_presave). "Drupal doet dat standaard al"
klopt dus niet voor de ingelogde route.
Opgehelderd 2026-09-21 (Bob, met de simplenews-instellingenpagina erbij): de app stuurt GEEN bevestigingsmail, en dat is correct. De drie nieuwsbrieven staan op opt-in/out-methode Double, en simplenews omschrijft die stand zelf als: "anonymous users receive an (un)subscription confirmation email. Authenticated users are (un)subscribed immediately." Een app-gebruiker is per definitie ingelogd, dus die hoort direct verwerkt te worden.
⚠️ Maar simplenews past die regel niet zelf toe. simplenews_subscribe_user()
bevat geen enkele check op de ingelogde gebruiker — hij doet blind wat de
$confirm-parameter zegt (gecontroleerd in de broncode). De aanroeper bepaalt
die waarde, en het websiteformulier doet dat met
simplenews_require_double_opt_in($tid, $account): FALSE zodra het
mailadres van de ingelogde gebruiker zelf is, anders de opt-in-methode van de
nieuwsbrief. De resource roept sinds 2026-09-21 precies diezelfde functie aan,
dus de app volgt de website automatisch — ook als de opt-in-instelling ooit
wijzigt. Read-only geverifieerd op productie: voor de ingelogde gebruiker geeft
hij false op alle drie de nieuwsbrieven, voor een vreemd adres true.
De variabele custom_nieuwsbrieven_confirm blijft bestaan als noodrem (niet
gezet = automatisch, 1 = altijd mail, 0 = nooit), maar is normaal niet nodig.
Uitschrijven gaat altijd direct, ook als bevestiging aan zou staan: anders drukt de gebruiker in de app op "uit" en blijft hij abonnee tot hij een mail opent.
De resource geeft toch de STATUS terug, niet een kale bool (1 aan / 2 wacht op bevestiging / 0 uit). Dat kost niets en houdt de deur open.
Van 450 anonieme inschrijvingen is er in tien jaar geen enkele bevestigd, terwijl alle 45 account-inschrijvingen meteen op actief staan:
| anoniem (uid 0) | met account | |
|---|---|---|
| status 1 | 0 | 45 |
| status 0 | 0 | 3 |
| status 2 | 450 | 0 |
Dat leek op een kapotte mailroute, maar het is exact de tweedeling die de Double-stand voorschrijft. Raakt de app niet. Wat blijft staan is een lage conversie op de publieke website-formulieren (0 % bevestigd) — ooit misschien een eigen kijkje waard, geen taak.
nieuwsbrieven · Eigenaar: Bob✅ 128-A IS KLAAR — gedeployd en end-to-end getest op productie (Bob,
2026-09-21). nieuwsbrieven.json levert de juiste lijst (zonder de rol
Horeca-owner zie je 2 van de 3), en subscribe.json gaf
{"tid":"36667",…,"status":1,"geabonneerd":true} — direct actief, geen
bevestigingsmail, precies zoals bedoeld. custom_nieuwsbrieven_confirm staat op
FALSE.
Zonder X-CSRF-Token antwoordt het endpoint met ["CSRF validation failed"]
(gemeten 2026-09-21; mijn eerdere inschatting dat de token niet afgedwongen werd,
was fout). Met de token erbij werkt het meteen.
De app is hier al op ingericht, mits je het bestaande patroon volgt:
drupalRequest zet de header zodra het 5e argument gevuld is, en
drupalLogin schrijft de token bij het inloggen naar FFAppState().userToken
(lib/custom_code/actions/drupal_login.dart:45). Geef bij de subscribe- en
unsubscribe-actie dus exact dezelfde vijf argumenten mee als het
favorieten-hartje op HorecagelegenheidCurrent:
userSessionname, userSessionid, userToken, plus de body.
Vergeet je die token, dan faalt het STIL. drupalRequest geeft bij elke
non-2xx gewoon [] terug (regel 143), dus er komt geen foutmelding: de switch
lijkt te werken en er gebeurt niets. Controleer een nieuwe actie daarom met
adb logcat | grep "I flutter" — die action print STATUS en de body.
unsubscribe.json is nog niet één keer aangeroepen. Zelfde route als
subscribe, dus laag risico; de app-test dekt het vanzelf af./admin/config/services/simplenews (Description van de nieuwsbrief).source = app in {simplenews_subscription} is voortaan via de
app binnengekomen — gratis telling, stond op 0 vóór de eerste test._custom_nieuwsbrieven_zichtbaar() is de spil en wordt door alle drie de
callbacks gebruikt: hij geeft simplenews_category_get_visible() terug (dat
laat hidden-nieuwsbrieven automatisch weg), minus tid 18017 wanneer de
gebruiker de rol Horeca-owner (rid 5) niet heeft — Bob 2026-09-21: selecteer
op rol, niet op het bezit van een horecagelegenheid. Er zijn 11 accounts met die
rol, waarvan er nu 1 op de ondernemersnieuwsbrief zit.
Door die filtering server-side te doen heeft de app géén conditie nodig, en
kan een gemanipuleerd verzoek zich er ook niet op abonneren — mits
subscribe/unsubscribe de binnenkomende tid tegen diezelfde lijst controleren
(if (!isset($lijst[$tid])) return services_error('Onbekende nieuwsbrief', 400);).
Zonder die guard kan een client zich op een verborgen nieuwsbrief abonneren.
Subscribe/unsubscribe zijn daarna drie regels elk, met
simplenews_subscribe_user($user->mail, $tid, FALSE, 'app'). Gebruik source
'app' (niet 'website'), dan is later meetbaar hoeveel abonnees uit de app
komen. Geef als respons dezelfde rij terug als in de index, zodat de app de
nieuwe status meteen kan tonen.
⚠️ Resource aanzetten is een APARTE stap na het deployen: in
/admin/structure/services/list/flutterdrup/resources de regel nieuwsbrieven
aanvinken én de drie losse operaties eronder — precies waar
mijn_aanmeldingen maandenlang op 404 stond. Daarna cc all.
Verificatie: anoniem curl moet 403 geven (route bestaat, sessie
ontbreekt), niet 404.
⚠️ Nooit edge-cachen — user-specifiek. Het nieuwe pad valt buiten de
bestaande Cloudflare Cache Rules (die matchen op /flutterdrup/views/…,
plaatsen), dus dat gaat vanzelf goed; het mag er alleen nooit bij.
Staat live op mijnProfiel, achter Account, getest op een toestel met
bobcity ingelogd: lijst laadt, aan- en uitzetten geeft 200 en de rij springt
meteen om. Opbouw zoals gebouwd:
Nieuwsbrieven (GET, Cookie: '${sessionName}=${sessid}'),
Predefined Path items = $[:] (Is List) → null-veilige
NieuwsbrievenCall.items(...)?.toList() ?? [].Column > ListView met Backend Query + Generate Dynamic Children.Row (padding 16/10/16/10) met Column > [Text $.naam
(Title Small), Text $.omschrijving (Body Small, secondaryText)] en rechts
twee conditionele IconButtons: toggle_off (grijs) → subscribe,
toggle_on (Primary) → unsubscribe. Conditie = custom function
nieuwsbriefAan(item), met/zonder Apply Opposite Statement.drupalRequest('POST', '…/nieuwsbrieven/(un)subscribe.json', …,
FFAppState().userToken, '{"tid": <$.tid>}') + Rebuild Page.⚠️ De omschrijving van tid 36667 is in Drupal gelijk aan de naam ("Uitgaanskrant voor bezoekers van de horeca" staat er twee keer onder elkaar). Geen app-bug — op te lossen in de term-beschrijving. Eigenaar: Bob.
Staat onder de lijst: een rode regel "Je volgt nog geen gemeenten, dus de
wekelijkse agenda blijft leeg. Tik hier om gemeenten te kiezen." achter
if (!(FFAppState().favorieteGemeenteIds.isNotEmpty)), met een Navigate To
naar selectprovinciegemeente. Daaronder staat altijd de oranje regel "De
wekelijkse uitgaansagenda gebruikt de gemeenten die je volgt…" met dezelfde
navigatie. Beide vertaald (NL+EN).
⚠️ Bekende beperking: favorieteGemeenteIds wordt alléén gevuld door
SelectStateDropDownComponent (die haalt in zijn On Page Load
favorieten_gemeenten.json op en schrijft de nids naar App State). Het veld is
persisted, dus in normaal gebruik klopt het — maar wie zijn gemeenten op de
website of op een ander toestel heeft gekozen en het gemeente-scherm in
deze app nog nooit opende, ziet de waarschuwing ten onrechte. Live gezien op
emulator-5554 met bobcity: warning stond er, en verdween zodra het
gemeente-scherm één keer geopend was.
Wat de fix zou zijn (uitgezocht, niet gebouwd): op mijnProfiel in de
On Page Load hetzelfde lijstje ophalen. Dat kan niet met drupalRequest:
die custom action geeft dynamic terug en FlutterFlow biedt zo'n output niet
aan als bron voor een getypt veld — getest op zowel een List<String>
App-State-veld als een List<Json> page state, in beide gevallen rendert de
bronnenlijst leeg. Twee werkende routes:
FavorietenGemeenten (GET + Cookie-header, net als
Nieuwsbrieven) → Update Page State favGemeenten met Action Outputs →
JSON Body → No Further Changes; conditie dan op die page state; offavorieteGemeenteNids een non-nullable return geven (List<String> in
plaats van List<String>?), waarna hij wél als bron verschijnt voor
Update App State → favorieteGemeenteIds → Set Value. Dat haalt meteen de
! weg uit SelectStateDropDownComponent.Het page-state-veld favGemeenten (List<Json>) staat al klaar op
mijnProfiel — aangemaakt voor route 1, nu nog ongebruikt.
Wie schrijft dat veld? Precies één plek (gemeten op een verse export):
SelectStateDropDownComponent (+ zijn twee dode kopieën) vult
FFAppState().favorieteGemeenteIds in zijn On Page Load. Favorieten tab 2
doet dat NIET — die leest zijn lijst rechtstreeks van de server. Wie dus
alleen Favorieten opent en nooit het gemeente-keuzescherm, houdt een leeg veld.
Dat maakt 128-E een mooie kans: als de tekst daar tóch komt, kan diezelfde tab
meteen ook App State bijwerken.
Wens Bob 2026-09-21. Op Favorieten, tab 2 (Favoriete gemeenten), onder
de lijst met gemeenten een tekst met een link naar de nieuwsbrieven-tab op
mijnProfiel.
Technisch: dit is de enige plek in het project waar naar een SPECIFIEKE tab
genavigeerd wordt — dat bestaat hier nog niet. Alle vier de TabControllers
(mijnProfiel, home, favorieten) staan op een hardcoded initialIndex: 0,
en er is nergens een animateTo. Recept:
mijnProfiel een page parameter toevoegen (bv. tabIndex, Integer,
optioneel, default 0).Navigate To → mijnProfiel met tabIndex = de index
van de nieuwsbrieven-tab.⚠️ Bind aan de PAGE PARAMETER, niet aan een page state die je in On Page Load
vult. De TabController wordt in initState aangemaakt, dus vóór de
On-Page-Load-keten (die draait in een addPostFrameCallback). Een page state is
op dat moment nog leeg en de tab opent alsnog op 0 — hetzelfde patroon als bij
de dropdown-valkuil elders in dit bestand.
Tekst (Bob 2026-09-21: je-vorm, consistent met de rest van de app):
Alles wat in de tab staat is NL+EN ingevuld via het globe-icoontje, en de
5 EN-vertalingen die bij de tab-verhuizing gewist waren zijn hersteld:
Wachtwoord wijzigen/Account verwijderen/Uitloggen en de twee
Kopie-knoppen (die kregen bij de hernummering nieuwe sleutels,
oxse6aci en wcyy4pao — de sleutels die hier eerder genoteerd stonden
zitten in de dode kopie mijn_profiel_copy4).
Nog niet gebouwd: de regel bij status == 2 ("Check je mail om je
inschrijving te bevestigen"). Die stand komt met custom_nieuwsbrieven_confirm
= FALSE nooit voor; de resource geeft de status wel al terug, dus het is later
één conditionele Text erbij.
⚠️ De omschrijvingen van de nieuwsbrieven komen uit Drupal en krijgen dus géén globe en geen EN-vertaling. Dat is een bewuste beperking.
450 onbevestigde inschrijvingen (360 + 47 + 43). Die mensen hebben zich ooit
aangemeld maar nooit op de bevestigingslink geklikt en krijgen dus niets. Geen
app-probleem, wel het sterkste argument voor confirm = FALSE in de app.
Volledige doorloop op emulator-5556 (telefoon) en emulator-5554 (tablet)
met een verse release-APK uit een eigen export, zowel uitgelogd als ingelogd als
bobcity (Bob logde de telefoon halverwege in).
dart analyze gaf 0 errors; er is geen enkele exception of overflow in
logcat gevallen. Offline-gedrag, netwerkherstel, zoeken, datumfilters,
categoriefilter, login-foutafhandeling en de nieuwsbrief-toggles werkten alle
correct. Hieronder wat wél mis is, op volgorde van ernst.
HorecagelegenheidCurrent scrollt niet — halve zaakpagina onbereikbaarHet bestand bevat 0 SingleChildScrollView; de vier tabs zijn kale
Columns in een TabBarView. Op een telefoon (411 dp) past daar ná de
fotostrip, titel en tabbalk nog ongeveer één regel in, en de knop Alle
horecagelegenheden dekt de rest af. Gemeten op BAUT ZUIDAS (nid 50954): van de
Info-tab zijn telefoonnummer, e-mail en de volledige "over"-tekst niet te
bereiken — de regel Openingstijden … wordt middendoor gesneden en er is geen
scroll. Op tablet valt het minder op, maar het speelt daar ook.
Nuance na extra meting: de Agenda-tab scrollt wél (die heeft een eigen lijstwidget), maar ook daar legt de knop zich over de onderste kaarten heen. Het zijn dus twee losse dingen: (1) Info/Links/Bezorgen missen scroll, (2) de knop Alle horecagelegenheden overlapt op álle vier de tabs de inhoud in plaats van eronder mee te schuiven.
Fix: de Column van elke tab scrollbaar maken (let op het recept in CLAUDE.md:
eerst elke Expanded eruit, anders weigert FlutterFlow het).
PUitgaanPage, met een leeg zoekveldFFAppState().zoekterm is globaal, maar FilterBalkComponent maakt zijn
TextEditingController in initState leeg aan
(_model.textController ??= TextEditingController(), geen ..text = …).
Reproductie: op Home zoeken op jazz → menu → Gemeente → Uitgaan. De lijst
toont dan exact de 3 jazz-treffers van Amsterdam (nagemeten tegen
flutterflowmobiel1?zoek=jazz), terwijl het zoekveld Zoek op titel als
placeholder toont. De gebruiker ziet een vrijwel lege pagina zonder oorzaak.
FFAppState().zoekOpen = !zoekOpen, de controller
wordt gewist, zoekterm = '', dan herlaadLijsten().if (FFAppState().zoekOpen)) en op Home én PUitgaanPage
verdwijnt tegelijk de slider (if (!FFAppState().zoekOpen)).EasyDebounce 2000 ms → FFAppState().zoekterm = controller.text →
herlaadLijsten().zoekterm: FFAppState().zoekterm door aan hun
kaartcomponenten (Home 5×, één per tab; PUitgaanPage 1×), en die zetten hem
in hun Backend Query als query-parameter zoek.Alles hierin is App State — behalve de TextEditingController, en díe zit in het
component-model. Bij een paginawissel wordt dat model vers aangemaakt
(_model.textController ??= TextEditingController(), zonder text:), terwijl
zoekterm én zoekOpen blijven staan. Vandaar: veld open en leeg, filter actief.
⚠️ Tweede symptoom van dezelfde oorzaak: omdat zoekOpen ook meegaat, is op de
nieuwe pagina óók de slider verborgen. Dat maakt het beeld nog verwarrender.
zoekterm is niet persisted, dus een herstart van de app lost het vanzelf op
— wat verklaart waarom dit niet eerder opviel.
Zet op FilterBalkComponent → het TextField → Initial Value een binding
naar App State zoekterm. Dat is alles: het component wordt door beide
pagina's gedeeld, dus één wijziging dekt Home en PUitgaanPage.
Waarom dit de juiste richting is: het ontwerp is al "de filterbalk is globaal"
(zoekOpen reist immers ook mee). Dan hoort het veld te tónen waarop gefilterd
wordt, in plaats van de filter stilletjes te verbergen.
De bekende "Initial Value wordt te vroeg gelezen"-valkuil speelt hier niet:
het veld staat achter if (FFAppState().zoekOpen) en bestaat dus pas nadat de
gebruiker het zoeken heeft geopend — op dat moment is de App State al gevuld.
Builder-stappen:
FilterBalkComponent openen, het TextField in de tree selecteren.zoek (levert zoekterm, zoekActief
en zoekOpen op — bewust een term die meer dan één bron overhoudt, anders
rendert de optielijst leeg) → App State → zoekterm. Rendert de rij leeg:
hover er eerst vanaf een andere positie naartoe, in dezelfde tool-aanroep.Verwachte export:
_model.textController ??= TextEditingController(text: FFAppState().zoekterm).
Testen op een toestel: Home → vergrootglas → zoek jazz → menu → Gemeente →
Uitgaan. Het veld moet nu jazz tonen bij dezelfde drie treffers. Wis het
veld → lijst loopt weer vol (reken op de 2 s debounce).
Alternatief als je liever hebt dat zoeken per scherm is: zet in plaats
daarvan op beide pagina's een On-Page-Load-actie die zoekterm én zoekOpen
reset. Dat is meer werk (twee pagina's) en je verliest de zoekterm bij elke
navigatie — maar het is verdedigbaar, want het aanbod verschilt per scherm. Kies
er één; de combinatie van beide is zinloos.
Bob 2026-09-22: "als iemand stadsrechten heeft, mag de activiteit direct gepubliceerd worden. Alleen gewone gebruikers die een activiteit aanmaken, die moeten eerst in drupal worden goedgekeurd." De app-tekst klopt dus; de backend doet het niet.
Huidige stand: _custom_stadsactiviteit_create() zet onvoorwaardelijk
$node->status = 0 en neemt bewust géén status-argument aan, en de docblock
noemt mijn_stadsrechten letterlijk "PUUR INFORMATIEF … GEEN toegangscontrole".
Iedere app-inzending wacht daardoor op de redactie, ook die van bobcity (die
stadsrechten heeft op Amsterdam, Drechterland, Enkhuizen en Stede Broec).
Wat er moet gebeuren, en waarom het niet triviaal is:
status uit
de client, anders kan iedereen publiceren.field_town_access van de gebruiker met de gekozen plaats van de
activiteit. Let op het niveau-probleem uit CLAUDE.md: dat veld kan een
provincie-, gemeente- óf plaats-tid bevatten (bobcity heeft nu gemeenten,
eerder stonden er plaatsen en provincies in). Bepaal het niveau met
count(taxonomy_get_parents_all($tid)) → 1 provincie / 2 gemeente / 3 plaats,
en kijk of de gekozen plaats onder een van de rechten-tids valt.status = 1, anders status = 0.Pas ná deze wijziging klopt de introtekst (rupw26n4) met de werkelijkheid. De
tekst zelf hoeft dus niet aangepast; wel controleren zodra de backend om is.
Duplicate-erfenis)| bestand | sleutel | nl | en (fout) |
|---|---|---|---|
evenement_horecagelegenheid_widget.dart:1253 |
jxlwje5u |
Bezorgen | Opening hours |
idem :1465 |
usg8k534 |
Bezorgt in | Opening hours |
idem :1549 |
paqq93tp |
Betaalmogelijkheden | Opening hours |
horecagelegenheid_current_widget.dart:529 |
jwojezxv |
Agenda | User |
Verder zijn álle 219 gerenderde sleutels vertaald (0 lege en).
Op EventCurrent raakt de agenda-knop exact de schermrand (de deelknop
ernaast heeft wél ruimte), net als het categorielabel eronder. Op tablet valt
het favorietenhartje op HorecagelegenheidCurrent zelfs half buiten beeld.
Op een Home-kaart met 4 labels (die op 2 regels vallen) wordt de omschrijving
zonder "…" afgekapt — zie Ondernemend Dijk en Waard op de tab Cultuur & Info.
De labels eten de ruimte op die de responsive Max Lines veronderstelt. Bij één
label klopt het wel. Overweeg de labels op één regel te begrenzen, of de
omschrijving één regel korter.
Kies je een categorie zonder treffers, dan verschijnt een kaal logo800px.png.
LegeLijstComponent (Icon event_busy + Text) doet dit al goed op mijnProfiel.
Volledige inventaris — 18 plekken in LEVENDE code, 9 bestanden. De dode
kopieën (kanweg/*, home_copy, home_copy2, p_uitgaan_page_copy{,2,3},
drawer_component_copy, mijn_profiel_copy*, evenement/event) bevatten nog
~15 logo's; geverifieerd wees (alleen zelfreferenties), overslaan.
| Waar | Plekken | Dekt |
|---|---|---|
HomeUitgaantabelKaartComponent |
1 | alle 5 Home-tabs (één component) |
HomeUitgaanSliderComponent |
1 | Home-slider (Carousel) |
PUitgaantabelKaartComponent |
1 | PUitgaanPage-lijst |
PUitgaanSliderComponent |
1 | PUitgaan-slider (Carousel) |
MijnAanmeldingen |
1 | staat op uitgaanskrant.com-applogo.jpg |
horecagelegenhedenOverzichtCurrent |
6 | één per tab |
HorecagelegenhedenOverzichtProvinciePage |
6 | ⚠️ wél bereikbaar, drawer_component_widget.dart:1209 |
thuisBezorgen |
1 | |
HorecagelegenheidCurrent:248 |
1 | fotocarousel van een zaak — ander geval, beeld-placeholder verdedigbaar |
Daarnaast: Favorieten tab 1-3 hebben helemáál geen Empty List Widget (zie
132-H), en de agenda-tab van een zaak gebruikt een eigen component
GeenEvenementenComponentmoetweg (Icon + vaste tekst) — functioneel al goed,
opruimkandidaat.
Carousels: op #2 en #4 staat de toggle al aan (er staat immers een Image),
dus alleen Widget Type → Component. Reken er toch op dat dit er twee voor Bob
zijn; CLAUDE.md meldt die checkbox als voor Claude onbereikbaar.
De tekst-parameter is een letterlijke string, dus geen globe en geen
Engelse vertaling. Drie dingen uitgezocht:
FFLocalizations.getVariableText({nlText, enText}) bestaat al in
internationalization.dart — dat is wat FlutterFlow genereert voor een
niet-statische tekst. Nergens in gebruik.locale is in dit project ONBRUIKBAAR — altijd
null. MyApp._locale begint op null en setAppLanguage() heeft
nul aanroepers (er is geen taalkiezer). De werkelijke taal komt uit de
Localizations-delegate (systeemtaal), en die is in de builder geen bron. Een
Visibility-conditie op "huidige taal" werkt hier dus niet. Niet proberen.BuildContext); een custom
widget wel.Drie werkbare routes:
Duplicate Component van
LegeLijstComponent, tekst-parameter eraf, vaste Text + globe. Nul
condities, per aanroepplek alleen "Empty List Widget → Component → kies".
Teksten belanden in internationalization.dart, dus de vertaalaudit ziet ze.
Kosten: ~10-12 componenten in de lijst.soort-parameter (String) en N conditionele
Text-widgets, elk met eigen i18n-sleutel. Houdt de componentenlijst schoon,
maar kost 12× een Visibility-conditie met een letterlijke Second Value (het
bekende verstopte veld). Het patroon zelf is bewezen: mijn_aanmeldingen_widget.dart:408
heeft al if (widget!.type == 'go_out_event').getVariableText(nlText:, enText:) en twee
String-parameters. Minste fragiele klikwerk, maar de teksten staan dan niet in
internationalization.dart (geen globe, geen audit).Openstaand besluit voor Bob: D, C of A. Reken op ~10-12 unieke teksten en
22 aanroepplekken (18 hierboven + de 4 bestaande op mijnProfiel die van
tekst: af moeten bij route D of C).
Tab 1-3 tonen niets en geen uitleg; de uitnodiging "Met een account bewaar je
favoriete gemeenten…" staat op tab 4, twee tabs naar rechts. Verwijs vanaf de
eerste tab naar die knop (of toon LegeLijstComponent met die tekst).
functions.buildStempel()
staat onderaan de drawer én op Favorieten en toont onbekend zolang er
geen --dart-define=BUILD_TS=… meegaat. Weghalen vóór livegang, of vullen.MijnProfiel staan ruw (2026-09-22 06:06:00) terwijl de rest
van de app dinsdag 22 sep, 14:00 gebruikt. Geldt voor Mijn evenementen en
Eigen activiteiten.favorieten_widget.dart:435 geeft hardcoded inhoud: null mee. Nagemeten
2026-09-22 (respons uit logcat van de ingelogde app):
favorieten_agenda.json heeft géén omschrijvingsveld. Volledige veldenset:
node_type, nid, titel, datum, datum_raw, plaats, categorie, logo, adres,
postcode, woonplaats, horecagelegenheid, horecagelegenheidNid, matched_via.
De binding is dus niet "vergeten" — er valt niets te binden. Keuze voor Bob:
óf een omschrijvingsveld aan de resource toevoegen (Drupal), óf de
placeholderregel weghalen zodat de kaart gewoon zonder tekst staat.
(Terzijde: horecagelegenheidNid mét hoofdletter N is voor dít endpoint
correct — getest, het horeca-blok op de detailpagina komt gewoon door. Niet
"gelijktrekken" met de $.horecagelegenheidnid van de views.)PUitgaanPage en het
horeca-overzicht); op het horeca-overzicht legt de 468×60-banner zich over de
onderste kaart heen. Zie ook G0.6.Raakt taak 128. Op mijnProfiel → Nieuwsbrieven stond bij bobcity de rode regel
"Je volgt nog geen gemeenten, dus de wekelijkse agenda blijft leeg" — terwijl
die drie favoriete gemeenten heeft (Alblasserdam, Amsterdam, Arnhem, zichtbaar
op Favorieten tab 2).
De conditie zelf is goed
(if (!(FFAppState().favorieteGemeenteIds.isNotEmpty)), regel 2302). Het
probleem is de bron: favorieteGemeenteIds wordt op precies één plek gevuld,
in de On-Page-Load van SelectStateDropDownComponent
(select_state_drop_down_component_widget.dart:115, via favorieten_gemeenten.json).
Dat component staat alleen op Zoek stad. Wie na het inloggen direct naar
Mijn Profiel gaat, heeft dus een lege lijst en krijgt de verkeerde waarschuwing.
Reproductie en bewijs (2026-09-22, telefoon-emulator, ingelogd): Mijn Profiel → Nieuwsbrieven → rode regel staat er. Daarna menu → Zoek stad → terug naar Nieuwsbrieven → regel is weg. Twee keer zo gelopen.
Fix: mijnProfiel zelf de lijst laten ophalen in On Page Load en de conditie op
een page state hangen — exact het patroon dat elders op diezelfde pagina al
gebruikt wordt (page state type JSON + Is List, gevuld uit een Backend Call,
conditie Is Set and Not Empty; recept staat in CLAUDE.md). Een App State die
maar op één scherm gevuld wordt, is hier de verkeerde bron.
Let op: de tweede, oranje regel eronder ("De wekelijkse uitgaansagenda gebruikt de gemeenten die je volgt") staat er altijd en is de algemene toelichting. Met 0 gemeenten staan er dus twee bijna gelijke regels onder elkaar — overweeg er één van te maken.
Drie plekken, en één ervan doet het expliciet fout:
| # | Bestand | Nu |
|---|---|---|
| 1 | favorieten_widget.dart:552 |
context.pushNamed(HomeWidget.routeName) — navigeert letterlijk naar Home |
| 2 | select_state_drop_down_component_widget.dart:559 |
tik op favoriete gemeente → context.safePop() |
| 3 | select_state_drop_down_component_widget.dart:458 |
knop Toepassen → context.safePop() |
Bij #2 en #3 kom je terug op de pagina waar je vandaan kwam — meestal Home, want
de keuzepagina wordt geopend vanaf het headerlogo/de drawer. Kwam je van
PUitgaanPage, dan sta je daar mét de oude plaats-queryparameter: de
gemeente lijkt gewijzigd (de header leest gemeenteSelectNaam uit App State)
maar de lijst niet.
Bestemming: PUitgaanPage met plaats = FFAppState().gemeenteSelectId en
services = 'services_3' (= Uitgaan, het eerste item onder "Gemeente" in de
drawer; die navigeert al precies zo).
✅ Gemeten 2026-09-22: een gemeente-tid werkt gewoon als townid — dat was
het enige echte risico. Amsterdam gemeente 28694 geeft op services_3 50 items
en op services_4 5, tegen plaats 28695 respectievelijk 50 en 2. De gemeente
omvat meer plaatsen, dus dit is zelfs ruimer. Geen Drupal-werk nodig.
Aanpak per plek: twee acties achter elkaar — eerst safePop (keuzepagina
sluiten), dan Navigate To PUitgaanPage. Backstack wordt dan Home →
PUitgaanPage, dus de terugknop gedraagt zich normaal. Bij #1 vervangt de
Navigate gewoon de bestaande pushNamed(Home).
⚠️ Bijvangst-bug op dezelfde regels: favorieten_widget.dart:548 vult
provincieSelectName met $.parent_nid in plaats van $.parent_titel — dus de
provincienaam is er een getal. In select_state_drop_down_component_widget.dart:556
staat het wél goed. Meenemen bij deze taak.
Onderzocht 2026-09-22 — het is NIET de status, het is het komend=1-filter.
De lijst Eigen activiteiten (tab Stadseditor op mijnProfiel, regel 1180)
roept MijnAanmeldingenCall aan met type: 'activity', komend: '1', limit: 5.
Wat er goed is:
_custom_mijn_aanmeldingen() filtert niet op status — er staat
zelfs expliciet in het commentaar dat status-0 juist mee moet, en het levert
status + status_label ("Wacht op goedkeuring") uit.isOngepubliceerd() wordt alleen gebruikt voor een
snackbar bij aantikken (mijn_profiel_widget.dart:1285), niet voor zichtbaarheid.Wat het wél is: bij komend=1 doet het endpoint een INNER JOIN op
field_data_field_date met field_date_value >= vandaag. Gemeten voor bobcity
(uid 3621):
| nid | status | datum | komt door het filter? |
|---|---|---|---|
| 222823 | 0 | 2026-09-26 | ✅ (aangemaakt vandaag 15:03 UTC) |
| 222818 | 1 | 2026-09-25 | ✅ |
| 222819 | 0 | 2026-09-20 | ❌ verleden |
| 214445 | 0 | 2026-09-03 | ❌ verleden |
Dus 2 van de 3 ongepubliceerde vallen weg op datum, niet op status — en de enige die er wel doorkomt bestond vanochtend nog niet. Vandaar de indruk "alleen gepubliceerde".
Dat het juist ongepubliceerde treft is geen toeval: die liggen bij de redactie, dus hun datum kan verstrijken terwijl ze wachten — en dan verdwijnen ze stil uit het overzicht van de indiener. Dat is de echte bug.
Twee oplossingen:
komend leeg op déze lijst. Het
endpoint sorteert dan op created DESC — voor een "eigen activiteiten"-lijstje
eigenlijk logischer. Nadeel: verlopen gepubliceerde komen ook mee.Server-kant (aanbevolen, Eigenaar: Bob): laat komend=1 betekenen
"toekomstig óf nog niet gepubliceerd", in de if ($komend)-tak van
custom.mijn_aanmeldingen.inc:
$q->condition(db_or()
->condition('d.field_date_value', gmdate('Y-m-d'), '>=')
->condition('n.status', 0));
Raakt ook Mijn evenementen (type=go_out_event), wat daar hetzelfde
gewenste effect heeft.
& in de categorie · ✅ GEFIXT OP DEVBOB, productie nog te doenStand 2026-09-22: Bob heeft beide regels handmatig aangepast op devbob; Claude heeft het daar geverifieerd. Alleen de uitrol naar productie rest.
Geverifieerd op devbob (alles read-only gemeten):
php -l schoon op allebei;diff tegen productie toont alleen de bedoelde wijziging — plus twee
regels commentaar, één weggehaalde trailing space en één lege regel aan het
eind. Niets anders is bij het plakken meegekomen;'Kunst & Cultuur, Theater' → ["Kunst & Cultuur","Theater"],
en de dubbel geëscapete 'R&amp;B' → ["R&B"]. De komma-split blijft dus
intact en de herhaalde decode-lus werkt;favorieten_horeca live gedraaid voor uid 3621 (bobcity): Luxor Live →
["Kunst & Cultuur","Theater"] — precies het geval dat op productie nog fout
gaat. Accenten en slashes blijven heel (Muziekcafé/Poppodium, Theatercafé),
en een zaak met 7 categorieën blijft netjes gesplitst;mijn_horecagelegenheden live gedraaid voor uid 2 en 603: ook daar komen
Kunst & Cultuur en Feesten & partijen schoon door.Uitrollen naar productie: dezelfde twee regels in
/data/disk/o1/static/uitgaanskrant/sites/all/modules/custom/, daarna
drush @uitgaanskrant.com cc all. Geen Cloudflare-purge nodig — beide endpoints
zijn sessie-gebonden en matchen geen Cache Rule.
Opruimen: /data/disk/o1/tmp/fix-132k.py staat er nog (een script dat Claude had
klaargezet en dat Bob niet wilde gebruiken; het heeft niets gewijzigd, alleen een
droogdraai gedaan). Mag weg met rm.
Favorieten tab 3 toont bij Luxor Live het label Kunst & Cultuur.
Nagemeten in de rauwe respons:
"categorie":["Kunst & Cultuur","Theater"]. Dit is het bekende
_custom_clean_html()-patroon uit CLAUDE.md: het veld categorie staat voor
dit endpoint niet in de $fields_to_clean-lijst. De views leveren diezelfde
categorie wél schoon uit ("Kunst & Cultuur"), dus het is endpoint-specifiek.
Vindplaats opgezocht op de server (2026-09-22, alleen gelezen). Het zit op
twee plekken, met identieke code — beide bouwen hun resultaat zélf op uit
views_get_view('flutterflowmobiel_establishments') en draaien dus niet langs de
opschoning in custom_services_request_postprocess_alter(), ondanks wat de
docblock erboven suggereert:
| bestand | regel | resource |
|---|---|---|
custom.favorites_flag.inc |
316 | favorieten_horeca (Favorieten tab 3) |
custom.evenementen_aanmaken.inc |
306 | mijn_horecagelegenheden (Mijn Profiel) |
In beide gevallen krijgen titel, adres en plaats netjes
_custom_clean_html() mee en categorie als enige niet:
'plaats' => _custom_clean_html($plaats),
'logo' => str_replace('devbob.', '', $logo),
'categorie' => _custom_parse_komma_to_array($categorie_raw), // <- hier
Fix, één regel per bestand:
'categorie' => _custom_clean_html(_custom_parse_komma_to_array($categorie_raw)),
Decodeer ná het splitsen, niet ervóór. _custom_clean_html() handelt arrays
recursief af (custom.module:1860), dus dit werkt direct op de lijst. Zou je
$categorie_raw eerst decoderen, dan kan een , een extra komma opleveren
en daarmee de split op ', ' stukmaken.
Het tweede geval (mijn_horecagelegenheden) is niet visueel bevestigd — daarvoor
moet je eigen zaak toevallig een & in een categorie hebben — maar de code is
regel voor regel dezelfde, dus fix ze samen.
De volledige path op de server:
/data/disk/o1/static/uitgaanskrant/sites/all/modules/custom/.
Controleer in dezelfde moeite de andere favorieten-resources
(favorieten_agenda.json, favorieten_gemeenten.json) — in de opgevangen
agenda-respons zaten geen entiteiten, maar daar zat toevallig ook geen & in.
Offline starten (rode banner Geen verbinding met uitgaanskrant.com + werkende
knop Opnieuw, geen crash, geen grijs blok) · netwerkherstel · de vier
datumfilters (nagemeten tegen de API) · zoeken op titel · de tab-bewuste
categoriedropdown · login-foutafhandeling · de nieuwsbrief-toggles, die per rij
onafhankelijk schakelen en netjes subscribe/unsubscribe met CSRF-token
sturen (status 200) · EventCurrent inclusief het horeca-blok, ook als je er via
de persoonlijke agenda komt · de responsive 1/2/3-kolommen op tablet.
Bob 2026-09-21: niet in deze chat oppakken, alleen noteren.
Het bestaat wél — ik beweerde eerst van niet, op basis van de gecommitte
lib/, en dat was te snel. Een verse export van 2026-09-21 toont:
lib/custom_code/actions/drupal_request.dart herkent 401, en 403 mét
het woord "anonymous", maar alleen als er daadwerkelijk een cookie meeging
(een 403 zonder dat woord is een rechtenkwestie en mag niemand uitloggen);
hij zet dan FFAppState().sessieVerlopen = true.VerbindingsBanner staat op header_buttons_component_widget.dart:275, dus
in de gedeelde header.Wat er te verifiëren valt: of de banner in de praktijk ook echt verschijnt
bij een verlopen sessie, en of de overzichten dan niet leeg blijven (dat was de
aanleiding). Reproduceren kan door userSessionid in App State te vervuilen en
een pagina te openen die een drupalRequest doet (Favorieten, Mijn Profiel).
⚠️ Dit werk staat alleen in de builder, niet in git. Zie de waarschuwing hieronder.
lib/ en de builderGemeten 2026-09-21 met een verse export: diff -rq lib/ /tmp/ff-nb/lib/ geeft
86 regels. Daar zit onder meer in: de complete sessie-verlopen-afhandeling,
VerbindingsBanner in de header, de FilterBalkComponent-familie, een vierde
tab-indeling op mijnProfiel (Mijn evenementen / Stadseditor / Account,
waar de gecommitte versie nog losse blokken zonder tabs heeft) en
crashlytics_test.dart.
Niet gecommit door mij, want er liep op dat moment een andere FlutterFlow-sessie en dat werk kan half af zijn (staande regel: niet over andermans werk heen committen). Zodra die sessie klaar is, is een verse export
Ideeën die af zijn onderzocht maar bewust wachten. Bob 2026-09-20: eerst live, dan pas kijken of dit überhaupt de moeite is.
Bob's vraag: "ik kan een lijstje domeinen opgeven — maar hoe weet mijn app wat de URL is? een evenement, een horecagelegenheid, een stadspagina?"
Antwoord: Android geeft de VOLLEDIGE URL aan de app, en de app moet die zelf vertalen naar een route. Dat vertalen is het hele werk; het domeinlijstje bepaalt alleen of de app aan de beurt komt.
Daarom NIET uitgaanskrant.com registreren. Twee redenen:
/nl/Noord-Holland/Amsterdam/Bimhuis-Amsterdam). De app kan daar niets mee
zonder Drupal om een vertaling te vragen — een extra endpoint erbij.Wel uitgk.com. Dat is jouw eigen korte domein, het wordt alleen gebruikt door
de deelknop (event_current_widget.dart:414) en door zetInAgenda
(zet_in_agenda.dart:42), en het heeft één voorspelbare vorm: /<nid>.
Het type is met één API-call te bepalen — gemeten 2026-09-20:
flutterflow_events.json?nid= |
flutterflowmobiel_establishment_info.json?nid= |
|
|---|---|---|
| 222338 (event) | 1 rij, mét horecagelegenheidnid=75459 |
0 rijen |
| 75459 (zaak) | 0 rijen | 1 rij |
Dus: één call naar flutterflow_events.json?nid=X; komt er een rij uit, dan is het
een event en heb je de horecaid meteen te pakken (die heeft EventCurrent
als tweede parameter nodig). Nul rijen → behandel als zaak →
HorecagelegenheidCurrent(nid: X). Allebei leeg → Home.
Stadspagina's erbuiten laten. PUitgaanPage wil een plaats-tid, en die staat
in geen enkele URL — dat zou een derde lookup via plaatsen.json vergen voor
weinig winst.
Twee routes om uit te kiezen:
uitgk.com/222338) + een routerpagina in de app.
Mooiste links. Vergt een pagina met een pad-parameter op rootniveau (/:nid),
de API-call hierboven en een conditionele navigatie. ⚠️ Open vraag: kan
FlutterFlow een routePath /:nid op rootniveau aan zonder te botsen met /home,
/favorieten enz.? go_router matcht exacte paden eerst, dus het zou moeten
kunnen — maar dat is niet gemeten. Eén testpagina + een export geeft uitsluitsel.uitgk.com/eventCurrent?nid=..&horecaid=... Dan matcht
go_router het pad meteen en hoeft er in de app niets gebouwd te worden (het
custom scheme werkt al precies zo). Prijs: lange, lelijke deellinks, plus een
redirect-regel op uitgk.com voor wie de app niet heeft.Bob 2026-09-20: naar 2.0. "niet belangrijk voor de livegang, als het überhaupt belangrijk is." Terecht — de deelknop werkt al, hij opent alleen de website in plaats van de app. Pak dit pas op als blijkt dat mensen gedeelde links vaak volgen.
Volgorde-afhankelijkheid, en dus hoe dan ook na de launch:
/.well-known/assetlinks.json op uitgk.com moet de SHA-256 van het
Play-app-signing-certificaat bevatten, en dat certificaat krijg je pas ná de
eerste upload (Play Console → Setup → App integrity). Vóór die tijd is de
verificatie niet af te maken.
Elke tab-tik doet een verse fetch, ook als je terugkeert naar een tab die je al
bezocht hebt; heen-en-weer klikken tussen zes tabs haalt dus zes keer opnieuw op.
Een guard ("sla de fetch over als alleXxx al gevuld is") lost dat op, maar kost
een conditie in vijf actieketens — en juist die dialoog is op deze pagina fragiel.
Alleen doen als het in de praktijk hindert.
Besluit Bob 2026-09-22: de Kopie-knop onder de kaarten is te groot. Variant A
(icoon ín de rij) op mijnProfiel, variant B (compacte chip) op
MijnAanmeldingen, met tooltip, en de kleuren meteen goed.
Waarom hij zo groot oogt: alle drie de knoppen hebben
color: secondaryBackground op een kaart die zelf óók secondaryBackground is —
een onzichtbaar vlak van 40 dp met alleen het woord "Kopie" erin.
| # | Bestand | Structuur nu | i18n |
|---|---|---|---|
| 1 | mijn_profiel_widget.dart:617 (evenementen) |
Column [ ListTile(+chevron), Align(1,0) > Button ] |
oxse6aci |
| 2 | mijn_profiel_widget.dart:1430 (stadsactiviteiten) |
idem | wcyy4pao |
| 3 | mijn_aanmeldingen_widget.dart:386 |
Column [ Padding>Column(logo/titel/datum/status), Button ], knop gecentreerd (geen width) |
g4pbs1et |
Knop-opties op alle drie identiek: height 40, padding 16/0/16/0,
color secondaryBackground, titleSmall + primaryText, elevation 0,
borderRadius 8.
FlutterFlowIconButton, content_copy, buttonSize 34,
icon size 20, borderRadius 8, Fill Color primaryBackground,
Icon Color secondary (#FF680D — merk-DNA: oranje = actie). Rechts ín de
rij, met 8 dp rechtermarge (zie 132-E). De chevron (arrow_forward_ios_rounded)
vervalt: puur decoratief, de hele rij is al tikbaar.Button blijft, maar height 40 → 28, padding 16 → 10,
borderRadius 8 → 14 (pil), titleSmall → bodySmall, tekstkleur secondary,
borderSide 1 px secondary, Fill Color op transparant, en een
icoon content_copy vóór de tekst (iconPadding links 0, rechts 4).
Daarna Align(1,0) eromheen + 12 dp rechterpadding.triggerMode
longPress (niet tap — de knop heeft zelf al een onTap, anders dan het
favorietenhartje op HorecagelegenheidCurrent).
Referentiepatroon: horecagelegenheid_current_widget.dart:336-437.| plek | NL | EN |
|---|---|---|
| 1 en 3 | Kopieer naar een nieuw evenement | Copy to a new event |
| 2 | Kopieer naar een nieuwe activiteit | Copy to a new activity |
Bij A verdwijnt de knoptekst; oxse6aci en wcyy4pao blijven dan leeg staan
(niet erg — een lege en valt niet terug op NL, maar er is ook geen NL meer).
Bij B blijft g4pbs1et = Kopie/Copy gewoon staan.
ListTile → de juiste node (1e = evenementen, 2e = stadsactiviteiten).ListTile → Wrap Widget → Container, breedte op ∞ (het ∞-knopje in
het Width-veld; let op: dat is een toggle, één klik).Container → Wrap Widget → Stack.Align > Button de Stack in slepen (drop op de Stack-rij =
positie 0), daarna de Container-rij óók op de Stack-rij droppen zodat de
volgorde [Container, Align] wordt — anders ligt de knop áchter de rij.
Eén sleep per tool-aanroep, verse zoom ertussen.Align op (1.0, 0.0) + rechterpadding 8.Button → rechtsklik → Replace → IconButton. Dit is de kritieke
stap: Replace behoudt de actieketen. Zie de terugval hieronder als
IconButton niet in de Replace-lijst staat.ListTile → Trailing Icon weghalen (chevron) en contentPadding rechts
12 → 52, zodat een lange titel niet onder de knop loopt.IconButton in een Tooltip, tekst + EN via de globe.⚠️ Twee risico's, met terugval:
ListTile in een Stack is in dit project nergens precedent, en
FlutterFlow weigert een ListTile in een Row ("Invalid Action") omdat die
geen intrinsieke breedte heeft. Stap 2 (de Container met breedte ∞) is er
juist om dat te voorkomen. Weigert stap 3 alsnog: val voor plek 1 en 2 terug
op variant B — dan is de hele taak één set knop-opties + tooltip, zonder
structuurwijziging.Text biedt de dialoog
alleen RichText/TextField/Button). Lukt het niet: NIET Copy/Paste Action
Chain gebruiken — de keten hangt volledig aan het loop-item
($.nid, $.horecagelegenheidnid) en die bindingen sneuvelen bij plakken.
Terugval dan: de bestaande Button houden en hem icoon-only maken (knoptekst
leeghalen via de globe + content_copy als icoon + height/width 34,
padding 0). Zelfde resultaat, nul risico op verloren bindingen.Button selecteren → knop-opties uit "Doelstijl" zetten.content_copy).| check | nu | na |
|---|---|---|
grep -c "FlutterFlowIconButton(" lib/mijn_profiel/mijn_profiel_widget.dart |
2 | 4 |
grep -c "arrow_forward_ios_rounded" lib/mijn_profiel/mijn_profiel_widget.dart |
5 | 3 |
grep -c "AlignedTooltip(" lib/mijn_profiel/mijn_profiel_widget.dart |
0 | 2 |
grep -c "Stack(" lib/mijn_profiel/mijn_profiel_widget.dart |
0 | 2 |
grep -c "AlignedTooltip(" lib/mijn_aanmeldingen/mijn_aanmeldingen_widget.dart |
0 | 1 |
grep -c "FFButtonWidget(" lib/mijn_aanmeldingen/mijn_aanmeldingen_widget.dart |
1 | 1 |
Plus: height: 28.0 en borderSide in de opties van plek 3, en per plek dat de
actieketen intact is — tel de navigatieparameters
(grep -c "serializeParam" ... rond elke knop) en controleer dat $.nid en
$.horecagelegenheidnid nog aan het loop-item hangen, niet aan de respons.
*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, x64 voor de AVD's), uitgelogd (pm clear),
Nederlandse app-locale: 6 telefoon (1080x1731), 2x 7" tablet (1200x1812, AVD
tijdelijk op wm size 1200x1920 / density 240), 2x 10" tablet (2560x1456).
Status- én navigatiebalk weggecropt; alle ratio's onder 2:1.
📁 ~/uk-play-assets/screenshots/ + play-screenshots.zip; de oude set staat
in screenshots-oud-2026-09-19-middag/ (Claude gooit niets weg).
Opgelost t.o.v. de eerste set: header toont Heel Nederland i.p.v.
"Amsterdam (gemeente)"; de twee 10"-shots zijn nu echt verschillend (Home +
menu, md5 gecheckt); tel-05-zaak.png is het Bimhuis (nid 75459) mét
gevulde agenda.
🔴 Nog één keer over: tel-04-horeca.png toont nog de AdMob-testadvertentie
(showsTestAd: true in de export). Zodra Show Test Ads uit staat schiet
Claude die ene opnieuw — dat is 2 minuten werk, geen nieuwe build nodig
(deep link horecagelegenhedenOverzichtCurrent?plaats=28695).
🔸 Opgevallen, geen blokker: op de detailpagina's (tel-02-event,
tel-05/06-zaak) toont de header Amsterdam (de default
gemeenteSelectId), terwijl Home Heel Nederland zegt. Voor een event in
Steenwijk oogt dat wat vreemd; als je dat consequent wilt, is dat een aparte
taak op de header-keten.~/uk-play-assets/feature-graphic/DEFINITIEF-feature-graphic-1024x500.png
— 1024×500, RGB zonder alpha, 88 kB; voldoet aan de Play-eisen.
B = het complete app-icoon links als afgeronde tegel op leisteen
(#3E454C), rechts de naam + tagline + oranje accentstreep. Rood op
leisteen geeft het icoon contrast; de twee andere merkkleuren doen de rest.
Variant A (merkelement groot op merkrood) staat ernaast, niet gekozen.
feature.py staat erbij: tekst of kleur wijzigen is één regel + opnieuw
draaien. Rest nog: uploaden bij G3.2.#
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 kun je
NIET rechtstreeks uploaden: die is 1900×1900 en 1,1 MB, terwijl Play
exact 512×512 en max 1 MB eist (gemeten 2026-09-20).
✅ Klaargezet: ~/uk-play-assets/play-icon-512.png — 512×512, RGB,
110 kB, LANCZOS-verkleind uit datzelfde bronbestand (dat volledig
ondoorzichtig is, dus er is niets platgeslagen). Gebruik die, en niet
de adaptive foreground.
🔸 Opgevallen, jouw keuze: het woordmerk beslaat maar een smalle strook
van het rode vlak. Op 512 is het scherp, maar in de Play-lijst wordt het
icoon rond de 48 px getoond en dan is "Uitgaanskrant.com" niet meer te
lezen — je ziet dan vooral een rood vlak. Een variant met alleen het
kalendertje groot (of de tekst fors groter) leest daar beter; dat is wel
een ontwerpbesluit, geen fout.#
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.
Verse export bevestigt de body-Column van PUitgaanPage:
[slider (if !zoekOpen), Container > AdBanner, Expanded > FilterBalkComponent,
Expanded > lijst]. Precies de gevraagde volgorde.
Eindstand in de export: Container(height: 65/65/105/105) > Padding(top 15) >
FlutterFlowAdBanner zonder eigen width/height (dus adaptive). De banner houdt
daarmee zijn volle 50 dp op telefoon en 90 dp op tablet over, en de 15 dp lucht
tot de slider — die Bob er bewust in had gezet — blijft staan.
🔸 De zwarte laadtekst van FlutterFlowAdBanner blijft hoger dan de Container en
tekent dus even buiten zijn kader. Inherent aan de gegenereerde widget, niet vanuit
de builder te clippen. De layout beweegt niet meer, en dat was het doel.
Afgerond 2026-09-20 (Claude, in de builder, 19/19 geverifieerd met een verse export). Punt A (gewoon fout), punt B (technische veldnamen) en punt C (het sterretje bij de categoriekeuze) zijn gedaan op béide pagina's:
| was | is nu |
|---|---|
ACT kop "Categorie evenement" |
"Categorie activiteit *" / Category * |
EVT kop "Categorie evenement" |
"Categorie evenement *" / Category * |
| "Selectee..." | "Selecteer..." |
| "Datum eind" (4×) | "Einddatum" / End date |
| "WebsiteURL" (2×) | "Website" |
| "TicketsURL" | "Ticketlink" / Ticket link |
| "Toelichting Entree" (2×) | "Toelichting entree" |
| "Uw Horecagelegenheid" | "Je horecagelegenheid" / Your venue |
EN Zipcde · Organisatsion · Feeprice (2×) |
Zipcode · Organisation · Admission price |
EN Title cityactivity · Title cityactvity |
Activity title · Event title |
Nog open — schrijfkeuzes, bewust bij Bob gelaten:
Text-label
bóven het invoerveld én een labelText én een hintText; staan die alle drie
op hetzelfde woord, dan leest de gebruiker het drie keer. Bob heeft de losse
Text-labels van "Toelichting Entree" en "Entreeprijs" al weggehaald; over
zijn nog de label/hint-paren: ACT Einddatum · Adres · Postcode ·
Entreeprijs, EVT Einddatum · Toelichting entree · Entreeprijs. Een voorbeeld
in de hint doet meer werk dan een herhaling — "bijv. 7,50", "1012 AB".ACT EN Description activity tegenover EVT EN Event description.Select... staat er
nog 4× (ACT xiba4gka, l9f37oxb, ajotnz1f; EVT 2hitlrxa, ht4toe4o) en
Search... 1× (EVT qtnlg7nd), terwijl de categorie-dropdown op ACT wél
"Selecteer..." / "Zoek..." zegt. Mechanisch werk; Claude kan dit oppakken.EVT. De intro zegt "van je
onderneming", de kop en de dropdown zeggen "horecagelegenheid", de
hulpregel zegt "de zaak waar het evenement plaatsvindt". Eén woord kiezen
is een besluit voor Bob, geen opruimklus.stadsactiviteitAanmaken — Drupal-deel · Eigenaar: BobCode staat klaar en is getest (2026-09-20, Claude): vervang in
sites/all/modules/custom/custom.evenementen_aanmaken.inc de regels 316 t/m
372 (docblock t/m sluitende accolade van _custom_mijn_stadsrechten_service())
door de aangeleverde versie. Daarna drush @<alias> cc all.
Waarom: field_town_access mag een term op elk van de drie niveaus van
de town-vocabulary bevatten — plaats (tot 2026-08-27), gemeente (huidige
standaard) of provincie (de widget staat op Max depth 2 en laat dat toe; Bob
geeft ze bewust op provincieniveau, dat scheelt veel werk). De app stopte die tid
rechtstreeks in plaatsen_bij_gemeente, en dat geeft bij provincie én plaats een
lege lijst → zie taak 125.
De nieuwe versie geeft per recht de volledig uitgeklapte keten terug:
niveau · provincie_tid/_titel · gemeente_tid/_titel ·
plaats_tid/_titel · keten ("provincie|gemeente|plaats", ontbrekende delen
leeg) · label (bv. "Noord-Brabant (hele provincie)"). tid, titel,
parent_tid en parent_titel blijven ongewijzigd staan, dus bestaande
consumenten merken niets. Sortering is hiërarchisch i.p.v. alfabetisch op titel.
Getest op productie onder een testnaam, alle drie de niveaus:
| account | recht | keten |
|---|---|---|
| bobcity | 28694 Amsterdam (gemeente) | 28666\|28694\| |
| bobhoreca | 27409 Noord-Brabant (provincie) | 27409\|\| |
| Team Klein Frankrijk | 29912 Zuid-Holland (provincie) | 29912\|\| |
De eerste dropdown in het Waar-blok van stadsactiviteitAanmaken moet de drie
cascade-dropdowns voorvullen tot zover het recht reikt, in plaats van alleen
createGemeenteID te zetten:
| recht | voorvullen | gebruiker kiest nog |
|---|---|---|
| provincie | provincie | gemeente + plaats |
| gemeente | provincie + gemeente | plaats |
| plaats (oud) | alle drie | niets |
Stappen: dropdown-waarde omzetten van $[:].tid naar $[:].keten (label
op $[:].label), plus drie custom functions van één String-argument
(ketenProvincie / ketenGemeente / ketenPlaats → split('|')[n]). Die ene
parameter is bewust: een lookup op de hele respons vraagt een custom function met
meerdere argumenten, en die binding loopt in de Action Flow Editor structureel
vast (zie CLAUDE.md). On Selected → Update Page State voor
createProvincieID/createGemeenteID/createPlaatsID + Set Form Field op de
drie dropdowns.
Een lege Set Form Field-waarde wordt geweigerd ("Value cannot be empty"), dus
voor de lege delen van de keten is Reset Form Fields nodig — wat meteen de
tweede helft van taak 126 afhandelt.
Voorvullen, niet verbergen (besluit Bob 2026-09-20): met verbergen kun je achteraf niet meer wisselen zonder eerst de rechten-dropdown leeg te maken.
stadsactiviteit_aanmaken_widget.dart:1878 doet
(getJsonField(respons, r'$[:].plaatsid', true) as List?)!. getJsonField geeft
bij 0 matches null — ook met isForList: true, want de isEmpty-check
staat ervóór ([flutter_flow_util.dart:356]). Een lege respons [] geeft dus
Null check operator used on a null value; in een release-build een grijs blok
zonder melding.
Treedt nu op bij elke provincie- of plaats-tid in field_town_access, maar staat
los van taak 123/124: ook een gemeente zónder plaatsen in de taxonomie geeft
dezelfde crash. Zelfde patroon staat op de gemeente- en provincie-dropdown
van diezelfde pagina — alle drie langslopen.
Fix: Generate-Dynamic-Children-achtige null-veiligheid via een Predefined Path
op de API-call ($[:], Is List) i.p.v. een JSON Path — dat genereert
(XCall.items(jsonBody)?.toList() ?? []). Recept staat in CLAUDE.md.
createPlaatsID bij het wisselen van gemeente · Eigenaar: nader te bepalenGeen van de vier dropdowns in het Waar-blok reset de onderliggende. Scenario:
kies gemeente A → plaats A₁ (createPlaatsID gezet) → wissel naar gemeente B.
De Plaats-dropdown toont dan weer zijn hint (FlutterFlowDropDown filtert een
waarde die niet in de opties zit weg, [flutter_flow_drop_down.dart:101]), maar
createPlaatsID houdt A₁ vast. De verzendknop blijft dus zichtbaar en je
dient in voor de vorige plaats. Stil, geen foutmelding.
Fix: Reset Form Fields + Update Page State (Reset Value) op de onderliggende
velden in de On-Selected van provincie, gemeente én de rechten-dropdown. Overlapt
met taak 124 — samen oppakken.
lib/ · Eigenaar: Bob (Claude verwijdert niets)Gevonden 2026-09-20 bij het committen van de achterstallige working tree. Deze
mappen staan in de projectmap en in git, maar komen niet meer voor in een
verse export — ze zijn ooit in de builder verwijderd of hernoemd, en een export
ruimt de lokale map niet op (bekend patroon, staat in CLAUDE.md).
lib/favorieten_copy lib/kanweg_test_upload
lib/favorieten_copy2 lib/shared/drawer_component_copy
lib/backend/api_requests/get_streamed_response_web.dart (+ _http.dart)
lib/horecagelegenhedenoverzicht/horecagelegenheid_event_tabel_component_copy
lib/kanweg (+ kanweg_header_buttons_component_copy, lib/shared/geen_evenementen_component
kanweg_home_copy, kanweg_horecagelegenheden_overzicht, lib/shared/header_current_copy
kanweghorecagelegenheid_current_copy) lib/uitgaanspaginas/home_copy2
lib/uitgaanspaginas/home_uitgaan_slider_component_copy
lib/uitgaanspaginas/home_uitgaantabel_kaart_component_copy
Waarom het meer is dan rommel: ze vervuilen dart analyze.
Stand 2026-09-22 op een verse export in de projectmap: 7 errors, alle 7 uit deze
weesbestanden — 5× get_streamed_response_web.dart (importeert fetch_client,
dat niet in pubspec.yaml staat) en 2× KanwegHorecagelegenhedenOverzichtWidget
in kanweg/kanweghorecagelegenheid_current_copy en shared/drawer_component_copy,
omdat lib/index.dart die map niet meer exporteert.
⚠️ Nagemeten: de BUILD breekt hier niet van — niets in de levende code
importeert deze bestanden, dus de compiler komt er nooit langs (eerder stond hier
"harde compile-error"; dat klopt niet). Het kost je wel je nulmeting: zolang deze
mappen er staan is "0 errors" niet meer bruikbaar als controle na een export.
Er is er 2026-09-21 een derde bijgekomen, met een nieuwe oorzaak. De export
levert sindsdien alleen nog get_streamed_response.dart (die kaal http gebruikt);
de oude _web.dart/_http.dart uit de conditional-import-opzet komen niet meer mee.
Tegelijk haalde FlutterFlow fetch_api en fetch_client uit pubspec.yaml —
terecht, want niemand gebruikt ze nog. Gevolg: get_streamed_response_web.dart staat
er nog, importeert een package dat er niet meer is, en levert 5 analysefouten.
Niets importeert dat bestand (grep bevestigd), dus de build breekt er niet op —
alleen dart analyze. Samen met de twee hierboven: dart analyze op de projectmap
geeft 7 errors, terwijl een verse export er 0 geeft.
⚠️ Die twee errors stonden al op master vóór de commit van 2026-09-20 — het is dus geen regressie van die commit, maar ze blokkeren wel een lokale build vanuit de projectmap. Voor de release maakt het niets uit zolang je via FlutterFlow Deploy bouwt (dat vertrekt vanuit de cloud-staat, niet vanuit deze map).
Doen, als je de projectmap weer wilt kunnen builden:
cd /home/bob/Projects/ff-app/uitgaanskrant-1qhvtd
git rm -r --cached -q lib/favorieten_copy lib/favorieten_copy2 lib/kanweg lib/kanweg_test_upload lib/shared/drawer_component_copy lib/shared/geen_evenementen_component lib/shared/header_current_copy lib/uitgaanspaginas/home_copy2 lib/uitgaanspaginas/home_uitgaan_slider_component_copy lib/uitgaanspaginas/home_uitgaantabel_kaart_component_copy lib/horecagelegenhedenoverzicht/horecagelegenheid_event_tabel_component_copy lib/backend/api_requests/get_streamed_response_web.dart lib/backend/api_requests/get_streamed_response_http.dart
rm -rf lib/favorieten_copy lib/favorieten_copy2 lib/kanweg lib/kanweg_test_upload lib/shared/drawer_component_copy lib/shared/geen_evenementen_component lib/shared/header_current_copy lib/uitgaanspaginas/home_copy2 lib/uitgaanspaginas/home_uitgaan_slider_component_copy lib/uitgaanspaginas/home_uitgaantabel_kaart_component_copy lib/horecagelegenhedenoverzicht/horecagelegenheid_event_tabel_component_copy lib/backend/api_requests/get_streamed_response_web.dart lib/backend/api_requests/get_streamed_response_http.dart
fvm dart analyze lib/ | grep -c "^ error -" # hoort 0 te zijn
⚠️ Controleer vóór het uitvoeren dat favorieten_copy2 en header_current_copy
geen veiligheidskopieën zijn die je nog wilt bewaren — ze zitten sinds commit
98f0b74 in de historie, dus je kunt ze altijd terughalen, maar kijk er even naar.
Gemeten op de verse export van 2026-09-20 (dode kopieën uitgefilterd). Dit is de staart van P1-33: daar is destijds het rood opgeruimd, grijs en groen bleven staan.
| kleur | = token | aantal | waar |
|---|---|---|---|
Color(0xFFEEEEEE) |
Alternate | 9 | kaartranden op uitgaansevenementAanmaken (4) en stadsactiviteitAanmaken (5) |
Color(0xFF09B34A) |
Tertiary | 2 | HorecagelegenheidoverzichtKaart + tagCategorieComponent (het groene categorielabel) |
Nu onzichtbaar — de hexwaarden zijn exact gelijk aan de tokens, dus er verandert
niets aan het beeld. Het punt is onderhoud: wijzig je ooit alternate of
tertiary in Theme Settings, dan lopen deze elf plekken niet mee en krijg je twee
tinten door elkaar.
Doen: per widget het kleine kleurblokje vóór de kleurnaam aanklikken (niet de tekst — dan maak je er weer een hex van) → in "Choose a Color" onderaan de lijst Theme Colors → Alternate resp. Tertiary. Sluit met de X, niet met Escape (Escape laat de tree-selectie terugspringen naar de pagina-root).
Vindplaatsen, gemeten op de export van 2026-09-20 — het gaat steeds om de Border color van de kaart-Container om een formuliersectie, niet om de Fill:
| pagina | sectie waar de kaart bij hoort |
|---|---|
uitgaansevenementAanmaken |
intro ("Meld hier een evenement aan…") · zoekveld · "Datum eind" · "Link om kaarten te kopen" |
stadsactiviteitAanmaken |
intro ("Meld hier een activiteit aan…") · zoekveld · "Datum eind" · "Plaats activiteit" · "Website van de activiteit" |
De twee groene (09B34A → Tertiary) zijn wél Fill Colors:
HorecagelegenheidoverzichtKaart en tagCategorieComponent, allebei het
categorielabel.
De 12e kleur slaan we over: Color(0xFFFACFC6) in een gradient op
SliderUitgaanComponentSmallCurrent. Dat is roze, hoort bij géén token, en dat
component heeft nul gebruikers — het staat al op de opruimlijst.
⚠️ Claude kwam hier 2026-09-20 niet doorheen. Navigeren, panelen wisselen en de linkerrail werkten normaal, maar geen enkele klik op een widget-tree-rij selecteerde iets — ook de root niet, in ~10 pogingen op wisselende x/y. Een klik op de chevron ernaast deed wél iets (deselecteerde de root), dus de events komen aan; alleen de rij-selectie niet. Zonder selectie verschijnt het eigenschappen-paneel niet en is er geen kleur te zetten. Bob's venster was toen 1266x952; een breder venster is het eerste om te proberen.
Geen haast, geen blokker — prima klusje voor tussendoor.
Bob heeft Calendar uitgezet in App Settings → Permissions. Verse export bevestigt:
android/app/src/main/AndroidManifest.xml heeft nog vier permissies
(INTERNET, CAMERA, READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE) en
ios/Runner/Info.plist nog twee usage descriptions (Camera, PhotoLibrary).
READ_CALENDAR/WRITE_CALENDAR en NSCalendarsUsageDescription zijn weg.
Rest: de agendaknop op EventCurrent één keer testen op een echt toestel
(de AVD's kunnen dit niet). En bij een latere iOS-ronde de permissie terugzetten —
daar gebruikt add_2_calendar wél EventKit.
De app-kant is in orde — main.dart:32 roept adMobRequestConsent() aan en
flutter_flow/admob_util.dart doet de volledige UMP-flow
(requestConsentInfoUpdate → isConsentFormAvailable → ConsentForm.show).
Maar die flow doet niets als er in de AdMob-console geen bericht klaarstaat:
isConsentFormAvailable() geeft dan false en de app slaat het formulier stil
over. Gevolg in de EU: geen geldige toestemming, dus alleen niet-gepersonaliseerde
advertenties (lagere opbrengst), en Google kan de serving beperken.
Doen: AdMob → Privacy & messaging → EU regulations (GDPR) → bericht maken voor de app en op Gepubliceerd zetten. Daarna bij de eerste release-test controleren dat het scherm ook echt verschijnt bij een verse installatie.
⚠️ Niet te verwarren met de FlutterFlow-toggle. Bob liet 2026-09-20 zien dat
App Settings → AdMob → "Show GDPR Consent Dialog at App Launch" aan staat — dat
is de app-kant en die was al goed (die toggle genereert juist de
adMobRequestConsent()-aanroep in main.dart). De ontbrekende helft zit op
admob.google.com, niet in FlutterFlow. Zonder gepubliceerd bericht vraagt de
app netjes om een formulier, krijgt er geen, en gaat stil verder.
🔸 Terzijde uit diezelfde screenshot: Ad content filtering staat op Unset. Dat is AdMob's standaard en prima, maar het loont om er bij G2.5/G2.6 (content rating + doelgroep) bewust een waarde bij te kiezen zodat de advertenties passen bij wat je bij Play aangeeft.
De uitgelogde doorloop is af (2026-09-19, --release, flags=0x0): 0 FATAL,
0 E/flutter, alle iconen renderen, vliegtuigmodus geeft netjes de
VerbindingsBanner, Engels vertaalt. 65a, 65b en 65d zijn opgelost en
export-geverifieerd.
Wat overblijft — en dat is per definitie Bob's stuk, want Claude voert geen wachtwoorden in:
Meenemen in die doorloop (nieuw, 2026-09-20): de gebruikersnaam wordt nu
onthouden na uitloggen. Verwacht gedrag: inloggen → uitloggen → het bovenste
veld op Login staat al ingevuld met wat je de vorige keer typte, het
wachtwoordveld is leeg. Overleeft ook het volledig afsluiten van de app
(persisted). Het kruisje in het veld wist het voor een andere gebruiker.
🔴 De ingelogde doorloop. Nooit op een release-build getest, en het raakt de kernfuncties: inloggen · hartje op een zaakpagina (aan én uit) · Favorieten met inhoud, alle vier de tabs · gemeente volgen · evenement aanmaken tot en met indienen · Mijn profiel inclusief uitloggen. Een Play-reviewer logt straks wél in.
65c · ✅ af (nagemeten 2026-09-19 avond in een verse export): Favorieten toont
uitgelogd nu een uitlegtekst met inlogknop, achter een guard op userSessionid
(sleutel zxbjvxzq). Gebouwd in een andere sessie als taak 107.
65e · "Datum" blijft Nederlands in de Engelse modus — het label komt uit de
valueOrDefault-default 'Datum' en is dus geen vertaalsleutel. Hoort bij P1-17.
Cosmetisch, al bekend: het logo in de drawer staat deels onder de statusbalk op de release-build (op de profile-build van 2026-09-19 is dat niet meer zo — taak 100 zette de padding op 40; opnieuw bekijken bij de volgende release-build).
https://uitgk.com/<nid> (deelknop op EventCurrent en
de tekst in zetInAgenda) redirect naar de juiste pagina op uitgaanskrant.com.
Getest op drie nids, inclusief een horecagelegenheid: 222338 -> "Scott Hamilton
& Rein de Graaff Trio…", 75459 -> "Bimhuis Amsterdam". Eind-URL is een nette
/nl/Noord-Holland/Amsterdam/...-alias./nl/support/mobieleapp (de Support-link in de
drawer én de data-deletion-URL voor Play) en /nl/support/privacybeleid.devbob staat niet meer in de levende app-code (alleen in dode kopieën).Fase 1 én 2 staan op productie en zijn nagemeten. Van 42 naar 22
methodes op endpoint flutterdrup. Het bewezen lek is weg: node/<nid>.json
gaf anoniem 200 met 46 velden inclusief name (de inlognaam van de auteur,
dus user enumeration) en geeft nu 404.
Dicht (404): node/retrieve + relationships/files · file/create +
file/retrieve · taxonomy_term · taxonomy_vocabulary · user/retrieve|create|
delete|logout|token|user_pass_reset|cancel|password_reset|resend_welcome_email ·
system/connect · geocoder · userestablishments.
Blijft aan (22): de app-resources (plaatsen, plaatsen_bij_gemeente,
horecacategorieen, views, de favorieten-, aanmaak- en upload-acties,
mijn_*, categorieen, entreeopties, accountdelete) plus user/login en
user/request_new_password.
Regressiecheck na afloop, devbob én productie: alle vijf views-endpoints 200 ·
plaatsen/plaatsen_bij_gemeente/horecacategorieen 200 · POST user/login.json
401 op onzin · een afbeelding uit een view 200 image/jpeg.
📁 Script: /home/bob/Projects/services-dicht-fase2.php (droogdraai is de default,
doen als argument voert uit). Bewaren voor als er ooit een resource bij komt.
Alleen de vijf app-views geven 200; user_contents, places, _town_tree,
thuis_bezorgen, front_block_go_ca, _for_search_page, establishment_calendar,
flutterfavorietenagenda, _block_go_ca_for_town, block_more_go_out,
latest_photobook en _search_ca_page geven 404.
⚠️ De foutmelding luidt ["Display block_1 on view user_contents could not be
found"] terwijl die view wél een block_1 heeft — dat betekent hier "view staat
niet op de toegestane lijst", niet "display bestaat niet".
Alle vijf nagemeten op de verse export en op productie; niets te doen, hier genoteerd zodat niemand het opnieuw uitzoekt:
~/uitgaanskrant-test.apk met readelf -lW: libapp.so en
libflutter.so zijn 0x10000 (64 KB) uitgelijnd, libdatastore_shared_counter.so
0x4000 (16 KB) — allemaal ≥ 16 KB. ndkVersion 28.2.13676358 dekt dit.36 (android/app/build.gradle:62), ruim boven de Play-eis van 35./nl/support/privacybeleid
(200) noemt AdMob (2x), Crashlytics (3x), Firebase (5x), advertentie (6x) en
verwijderen (8x). Dat is precies wat Play's Data-safety-controle naast je
declaratie legt./nl/user/register
(de knop Account aanmaken op Favorieten) en /nl/user/password. Zelfde val als
bij G2.2, maar hier dus goed.main.dart:32) — of het bericht ook echt
verschijnt hangt af van de AdMob-console, zie taak 118.AdMob: Show Test Ads uitzetten. App Settings → AdMob (niet een vinkje
op het AdBanner-widget — dat paneel heeft alleen Ad Properties en Ad Banner
Dimensions; de export schrijft de projectinstelling per widget uit).
Twee dingen bij het omzetten: (1) reken op lege ruimte i.p.v. advertenties
zolang de app niet gepubliceerd is — AdMob levert dan nauwelijks fills, dat is
normaal; (2) klik daarna niet zelf op je eigen advertenties, dat telt als
ongeldig verkeer. Zodra hij uit staat schiet Claude tel-04-horeca.png opnieuw.
Builder-opruimronde, laatste restanten. Nog in de export: componenten
SliderUitgaanComponentSmallCurrent,
PUitgaantabelKaartComponentOrgineelMetKaartjeerin en
SelectStateDropDownComponentCopy2, de pagina mijnProfielCopy3 (staat alleen
als route in nav.dart, niets navigeert ernaartoe) en de custom function
dISABLEdatumVoorApi. (De laatste twee kopieën ontstonden 2026-09-20 bij het
oefenen op taak 2-4.)
⚠️ Pagina Event: laten staan, dit is een doodlopend spoor. Bob bevestigde
2026-09-20 dat óók hernoemen afketst op "Invalid Action" — verwijderen,
verplaatsen en hernoemen falen dus alle drie, ook in zijn eigen browser.
Nagemeten op de export (2026-09-20): de pagina is onbereikbaar. De enige
verwijzingen zijn de twee die FlutterFlow automatisch voor élke pagina
genereert (index.dart en de route-registratie in nav.dart); er is nergens
een goNamed/pushNamed naartoe. Geen enkele gebruiker komt er dus, behalve
via een handmatige deep link. Kosten van laten staan: één dode route in
nav.dart en ~30 kB in de app. Niet opnieuw agenderen. Moet in de builder, niet met
git rm — anders staat alles na de volgende export terug.
P1-17 · vertalingen: ✅ rond voor de app (2026-09-20). Nagemeten over alle
218 sleutels die in bereikbare widgets gerenderd worden: 0 met gevulde NL en
lege EN. De laatste zes zaten in FilterBalkComponent en zijn ingevuld.
tcyc3idz bestaat niet meer. Blijft over: de vier lege-lijst-teksten op
mijnProfiel, die zijn letterlijke parameterwaarden zonder globe.
⚠️ Tel geen sleutels mee waar EN == NL terwijl dat correct is (Select...,
Website, Facebook, Media, ", " — identiek in beide talen).
Titels met spaties vooraan/achteraan (was P1-47). Bob 2026-09-11: de
impact is te groot, we lopen de namen door bij de livegang. MySQL sorteert op
de rauwe node.title, dus zo'n node valt buiten de alfabetische volgorde
terwijl de JSON schoon oogt (_custom_clean_html poetst ná de query).
Telquery:
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 geneste TRIM op node én
node_revision, idempotent, met vooraf een sql-dump van die twee tabellen.
URL-aliassen blijven ongemoeid.
Max items + tab 6 (P2-24 A en C) — ✅ opgelost, alle zes de tabs draaien
op filterHorecagelegenheden met .take(1000). Niets meer te doen.
(Afgerond en daarom verwijderd uit dit blok: taak 56 opruim-cron · P1-30
Cloudflare rate limiting · P2-25 debug-cleanup custom.module · de twee dode
KANWEG-API-calls · de opruimronde van 7 dode kopieën.)
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:
horecaid=157070).
RowLogo (logo 96×96 + titel + categorielabel), de adres-Row [Icon, TextAdres, TextPlaats]
met spacing 6 ("Westerstraat 95 Enkhuizen"), telefoon, e-mail, de links-Wrap
(Menukaart/Website/Facebook/Instagram/X/Twitter/Op Uitgaanskrant.com) en TextInhoud
renderen allemaal goed.
✅ "Contact"-label gefixt (2026-09-19 avond): de Row staat nu op Cross Axis
Alignment start met Items Spacing 8, dus het label lijnt uit met de eerste regel
links i.p.v. tussen de regels te hangen. Export: crossAxisAlignment: start +
.divide(SizedBox(width: 8.0)).ColumnBezorgen mét echte data bekeken en grotendeels opgeknapt (2026-09-19).
Testzaak: nid 157070 (Chinees-Indische restaurant Lotus, Enkhuizen) — die heeft
álle bezorgvelden gevuld (bezorgtijden, bestellink, bezorgkosten, minimaleorder,
bezorgtin, thuisbezorgtbetaalopties, cryptocoins, afhaalopties) én menukaart,
openingstijden, openingstijdenuitzondering en kvk. Route:
uitgaanskrant://uitgaanskrant.com/eventCurrent?nid=214689&horecaid=157070.
Wat er mis was: de sectie toonde elf kale regels zónder één label — "vanaf 17 uur",
"2 euro", "20 euro", "Contant" zonder context, de bestellink als kale URL, geen kop, en
lijstwaarden die aan elkaar plakten ("AfhaalBezorgen", "EnkhuizenBovenkarspel").
Gefixt en op emulator-5554 (profile) geverifieerd: kop "Bezorgen" (i18n jxlwje5u,
dezelfde stijl als "Openingstijden"); labels via Combine Text op Bezorgtijden:,
Bezorgkosten: en Minimale bestelling:; een Visibility-guard op
Textbezorg-minorder (establishmentMinimaleorder is set/non-empty) — die had er géén,
dus met label erbij zou hij anders "Minimale bestelling: null" tonen; en Wrap-spacing 8
op de drie lijsten, zodat de waarden niet meer aan elkaar plakken.TextInhoud heeft nu een kop "Over deze zaak" (EN About this venue, sleutel rko8u7a5, Body Medium 600) in een nieuwe ColumnInhoud met padding-top 8 (2026-09-19 avond, export-geverifieerd).22xoyrqe) staat nu op Menu (2026-09-19 avond).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.EventCurrentCopy, EvenementHorecagelegenheidCopy, EvenementInfoCopy) op de P2-7-opruimlijst zetten zodra de nieuwe pagina goedgekeurd is. Claude verwijdert niets.#
Gemeten 2026-09-19 op de export; dit corrigeert een eerdere bewering van Claude
in deze sessie ("tot 60 requests per schermopening"). Die telling was fout: er
staan zes fetchAlleHorecagelegenheden-aanroepen in
horecagelegenheden_overzicht_current_widget.dart, maar ze worden niet samen
uitgevoerd.
addPostFrameCallback) doet er één — tab 0
(horcat 17969, Activiteiten, services_1).onTap: (i) async { [ () async {}, …vijf closures… ][i]() }
— dus per tab-tik precies één fetch, en tab 0 doet niets omdat die al geladen is.Een schermopening kost dus één fetchAlleHorecagelegenheden, en die loopt
door API-pagina's van 100 tot maxPaginas = 10. In Amsterdam heeft tab 0
(Activiteiten) 42 items → één HTTP-request. De 60 uit die eerdere schatting
komt in de praktijk nooit voor.
Gevolg voor de rate limit: 100 requests per 10 s is ruim voldoende; de verhoging naar 200 was niet nodig (schaadt ook niet).
Wat er wél nog te winnen valt, klein: elke tab-tik doet een verse fetch,
ook als je terugkeert naar een tab die je al bezocht hebt. Heen-en-weer klikken
tussen zes tabs haalt dus zes keer opnieuw op. Een guard ("sla de fetch over als
alleXxx al gevuld is") lost dat op, maar kost een conditie in vijf
actieketens — en juist die dialoog is op deze pagina fragiel. Niet gebouwd;
alleen doen als het in de praktijk hindert.
A, C, D en E zijn af (2026-09-19, export-geverifieerd): alle zes de
StaggeredViews draaien op filterHorecagelegenheden(...), .take(25) is weg
(6x .take(1000)), de routes wijzen naar ...Current, en de twee lege
TextField-placeholders zijn opgeruimd. F is geschrapt op Bob's verzoek.
B — categoriedropdown per tab: ✅ af (nagemeten 2026-09-19 avond): de
FlutterFlowDropDown op horecagelegenhedenOverzichtCurrent hangt aan
horcatOptiesVoorTab / horcatLabelsVoorTab met de TabBar-index. Daarmee is P2-24
helemaal rond; dit blok kan weg zodra Bob 'm op het toestel goedkeurt.
Niet oplosbaar, geaccepteerd: FlutterFlow zet op de On Change-trigger van
het zoekveld een EasyDebounce van 2000 ms die nergens instelbaar is. De
lijst ververst dus ~2 s nadat je stopt met typen.
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).
✅ Besluit Bob 2026-09-19: dit is een IMPORT-probleem en wordt bij de importer afgevangen. Geen Drupal-hook of extra display nodig; niet opnieuw agenderen. Wel bij een volgende contentmeting nakijken of het getal daalt (nu 618).
afgewezen alternatief
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.
De 403-detectie (2026-09-20) is volledig geverifieerd op code- en serverniveau:
export bevat alle onderdelen, dart analyze 0 errors, en productie antwoordt bij
een ongeldige cookie met 403 ["Toegang geweigerd voor gebruiker anonymous"] —
precies wat drupalRequest detecteert. Alleen nog niet met eigen ogen op een
toestel gezien. Geen aparte build voor starten; meenemen bij de eerstvolgende
keer dat er toch een build draait.
Test in drie stappen (~1 min):
Wis de sessie server-side — vervang <uid> door je eigen uid:
ssh aegir-o1 "drush @uitgaanskrant.com php-eval \"db_delete('sessions')->condition('uid', <uid>)->execute();\""
Ververs Favorieten in de app. Verwacht: rode balk "Je sessie is verlopen. Log opnieuw in." met knop Inloggen, en de pagina klapt naar zijn uitgelogde weergave.
Werkt het niet, dan zit het in laag 2 (VerbindingsBanner), niet in de detectie —
die is hierboven al bewezen. Controleer dan adb logcat | grep "SESSIE VERLOPEN":
staat die regel er wél, dan komt de melding niet door en is het de SnackBar-logica.
Doel: de gebruiker zijn inloggegevens door Android/iOS laten opslaan en invullen, zodat opnieuw inloggen twee tikken kost. De app slaat bewust géén wachtwoord op — dat blijft bij het besturingssysteem.
Context: sessies zijn sinds 2026-09-20 een jaar geldig (zie CLAUDE.md),
dus dit is comfort, geen noodzaak. Los oppakken, niet meeliften op ander werk.
Stap 1 — invullen (FlutterFlow-property, laag risico).
Pagina login, beide TextFormFields:
| veld | herkenbaar aan | Auto Fill Hint |
|---|---|---|
| gebruikersnaam | obscureText: false, regel ~227 in de export |
username |
| wachtwoord | obscureText: !_model.passwordFieldVisibility, regel ~402 |
password |
Zit in Properties Panel → Additional Properties → Auto Fill Hint (toggle
aan, dan de optie kiezen). Kies username en niet email: Drupal 7's
user/login accepteert standaard alleen de gebruikersnaam.
Stap 2 — opslaan aanbieden (custom action, hier zit het risico).
Alleen hints is niet genoeg: het OS vraagt pas "wachtwoord opslaan?" na
TextInput.finishAutofillContext(). FlutterFlow genereert die aanroep niet en
documenteert hem niet. Nieuwe custom action autofillAfronden, zonder
argumenten, aan te hangen ná een geslaagde drupalLogin:
import 'package:flutter/services.dart';
Future autofillAfronden() async {
TextInput.finishAutofillContext();
}
Open vraag: werkt dit zonder een AutofillGroup om beide velden heen?
FlutterFlow genereert die niet zichtbaar, en Android is daar in Flutter
historisch kieskeurig in. Lukt het niet, dan is de enige route een custom widget
dat het hele loginformulier vervangt — dat is een flinke ingreep en waarschijnlijk
niet de moeite waard voor puur comfort.
⚠️ Testen kan NIET op de emulators. De tablet-AVD heet
Medium_Tablet_no-google: geen Play Services, dus geen Google Password Manager.
Dit moet op Bob's fysieke Xiaomi, met een profile-build.
Terugdraaien (per stap, beide onafhankelijk):
autofillHints: uit lib/login/login/login_widget.dart —
controleer met grep -c autofillHints lib/login/login/login_widget.dart,
hoort 0 te zijn.grep -rc finishAutofillContext lib/ hoort 0 te geven.Geen van beide stappen raakt de sessie-afhandeling, drupalLogin of App State,
dus terugdraaien kan niet stukmaken wat nu werkt.
#
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-14 | 09-19 | |
|---|---|---|
unieke events (flutterflow_events) |
16.065 | 17.123 |
| toekomstige events (op datum+tijd) | 6.202 | 6.684 |
| bereiken geen enkele app-tab | 622 (10 %) | 618 (9 %) |
| ... daarvan zonder categorie | 597 | 618 — dus 100 % |
| ... daarvan mét categorie | 25 | 0 |
De displays dekken alle categorieën nu volledig af — er is geen categorie meer die nergens landt. Het hele resterende lek is één oorzaak: een event zonder categorie valt door elk display-filter. Zie het blok bij taak 28/109; Bob vangt dat af bij de importer.
Ouderdomsverdeling (voor de opruim-cron van 180 dagen): 6.728 toekomstig · 8.107 jonger dan 180 dagen · 2.251 tussen 180 en 360 dagen · 14 ouder dan 360. Die 2.251 worden bij de eerstvolgende cronruns opgeruimd; met 1000 per run is dat ~3 dagen.
HTML-entiteiten: titels, adressen en de horeca-agenda zijn schoon (taak 129).
body heeft er nog 41 per 200 records, maar die gaat door de custom widget
FromHTML en rendert dus goed.
⚠️ Meetles: classificeer op datetime, niet op datum. Op datum alleen gaf
dezelfde meting 30 "bugs" die in werkelijkheid events van vandaag waren die al
begonnen waren — precies wat het >= -2 uur-filter hoort weg te laten.
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.
Gevraagd bij taak 28/109 ("bij een volgende contentmeting nakijken of het getal
daalt, nu 618"). Voor het eerst geclassificeerd op datum_iso in plaats van
op de Nederlandse datum-string — flutterflow_events levert dat veld sinds
2026-09-19 en is mét display_id=services_1 gewoon pagineerbaar (25/pagina).
| 09-12 | 09-14 | 09-19 | 09-20 | |
|---|---|---|---|---|
| unieke events | — | 16.065 | 17.123 | 16.080 |
| toekomstige events | — | 6.202 | 6.684 | 6.397 |
| bereiken geen enkele tab | 597 | 622 | 618 | 584 (9 %) |
| ... zonder categorie | 597 | 597 | 618 | 584 — 100 % |
| ... mét categorie | — | 25 | 0 | 0 |
Conclusie: 618 → 584, een daling van 34. Het liep eerder op (597 → 618), dus
de importer-aanpak van Bob lijkt te werken. De categorie-dekking van de zeven
displays blijft volledig: er is geen enkel toekomstig event dat wél een categorie
heeft en toch nergens landt. Voorbeelden van de rest: 222806 Steve 'n' Seagulls
(20 dec, Weert) · 222737 Altruism Label Night (21 nov, Maastricht) · 222703
BRECHTJE Maresa (1 okt, Groningen).
Twee bijvangsten:
datum_iso en vallen dus buiten élke
datumclassificatie. Klein, maar het betekent dat ze ook door het
>= -2 uur-filter van de master niet betrouwbaar te plaatsen zijn.services_2 geeft nu 0 rijen (stond in de notities nog op 25). Die display
staat al als "ongebruikt" te boek, dus geen impact — wel goed om te weten vóór
iemand 'm ooit wil hergebruiken.Alles wat buiten de app-code om stil kan zijn verlopen en een Play-inzending of de app zelf zou breken. Alles groen op één na (G3.1, zie daar).
| check | uitkomst |
|---|---|
/nl/support/privacybeleid (G2.1) |
200 anoniem |
/nl/support/mobieleapp (G2.2) |
200 anoniem |
/nl/account-verwijderen |
403 anoniem — daarom terecht NIET de data-deletion-URL |
deel-URL uitgk.com/<nid> |
200 op drie echte toekomstige events, landt op de juiste eventpagina |
| dichtgezet API-oppervlak | node · file · taxonomy_term · system/connect · user/<uid> allemaal 404 |
POST user/login.json met onzin |
401 (niet 404 — de route leeft dus nog) |
| de vijf app-views | alle 200 mét hun display_id |
plaatsen · plaatsen_bij_gemeente · horecacategorieen |
200 |
| Play-screenshots (10) | alle binnen 320-3840 px en ratio ≤ 2:1 |
| feature graphic | exact 1024×500, RGB zonder alpha, 88 kB |
⚠️ Meetval, kostte mij twee rondes: een views-endpoint zónder display_id
geeft 404 ["Display default on view X could not be found"]. Dat lijkt op een
dichtgezette resource maar is gewoon een onvolledige aanroep — roep een view
altijd aan zoals de app 'm aanroept. Idem flutterflow_events met het nid van
een horecagelegenheid: dat geeft terecht [], geen fout.
📌 uitgk.com/222338 uit de oude notitie klopt niet meer — die node bestaat
niet en de forward landt dan op de zaakpagina. Geen bug; test zoiets met een nid
uit een verse flutterflowmobiel1-aanroep.
Verse export + de patroonchecks uit CLAUDE.md. dart analyze op de verse
export: 0 errors. De twee gevonden punten zijn diezelfde dag opgelost en
export-geverifieerd:
Container om
Text-categorie in HorecagelegenheidEventTabelComponentCurrent stond op
Color(0xFF42E31C); nu op het thema-token Tertiary (#09B34A), gelijk aan
elk ander categorielabel. grep -rn "0xFF42E31C" lib/ geeft projectbreed 0.FilterBalkComponent zijn gevuld — Search by title · All · Today ·
Weekend · This week · initial option All. Nagemeten over alle 218
i18n-sleutels die in bereikbare widgets gerenderd worden: 0 met gevulde NL en
lege EN (was 6).
⚠️ Waarom "Weekend" tóch ingevuld moest: een lege en levert bij
getText() een lege string op, dus het chiplabel zou in het Engels
onzichtbaar zijn — EN==NL is hier geen reden om over te slaan.
✅ Het datumfilter blijft in het Engels werken: filterDatumVan/
filterDatumTot matchen op substring en kennen today al; This week bevat
week en All matcht niets (= geen filter). Dat is geverifieerd vóór het
vertalen, want de chip-labeltekst is tegelijk de waarde die in
FFAppState().datumFilter belandt.Blijft open (niet van Claude):
lib/ die de
verse export niet meer produceert. export-code schrijft alleen, verwijdert
nooit. Daardoor geeft dart analyze lokaal 2 errors (in
kanweg/kanweghorecagelegenheid_current_copy en shared/drawer_component_copy,
allebei Undefined name 'KanwegHorecagelegenhedenOverzichtWidget') terwijl de
verse export er 0 heeft. De build valt er niet over — niets importeert die
twee — maar het maakt de standaard "0 errors"-check onbetrouwbaar.
Advies: commit de huidige working tree niet as-is; draai een verse export en
commit die. Claude verwijdert niets — de mappen:
favorieten_copy · favorieten_copy2 · kanweg/ (8 bestanden) ·
kanweg_test_upload · shared/drawer_component_copy ·
shared/geen_evenementen_component · shared/header_current_copy ·
horecagelegenhedenoverzicht/horecagelegenheid_event_tabel_component_copy ·
uitgaanspaginas/home_copy2 · uitgaanspaginas/home_uitgaan_slider_component_copy ·
uitgaanspaginas/home_uitgaantabel_kaart_component_copy.Nagemeten en schoon — niet opnieuw doen:
! ?? in models (0) · getJsonField(...).toList() zonder true (0) ·
lege responsive-tak return 0; (0) · bron/pad-mismatch bij getJsonField (0) ·
ellipsis zonder maxLines (alleen in twee weeskomponenten) ·
ongelezen component-parameters (alleen de drie al bekende) ·
iOS UsageDescription staat op precies 3 · hardcoded kleuren in levende
widgets: alleen nog #09B34A/#EEEEEE (merkkleuren, niet getokeniseerd) en
transparante/schaduwwaarden.
showsTestAd: true staat nog aan — het restant van G0.6 (tel-04-horeca.png)
is dus nog steeds geblokkeerd op de AdMob-schakelaar.
Besloten 2026-09-21 na onderzoek. De site gebruikt al OneAll Social Login
(module 7.x-2.12, subdomein uitgaanskrantcom); 42 van de 298 accounts hebben
een koppeling (Facebook 29, Google 14, GitHub 3, Twitter/X 3, Microsoft 3). Nu
ook in de app.
Gekozen route: Direct Connect. Eén URL per netwerk, geopend in een Chrome
Custom Tab:
https://uitgaanskrantcom.api.oneall.com/socialize/connect/direct/facebook/?service=social_login&callback_uri=<urlencoded>
OneAll doet de OAuth en redirect naar de callback met ?connection_token=….
⚠️ Direct Connect zit niet in het Starter-plan (getest: "Direct Connect is
not available in the Starter plan") — vandaar de upgrade naar Personal
Advanced.
Twee dingen die dit ontwerp bepalen — niet omheen te bouwen:
disallowed_useragent), Facebook ook. Het moet een Custom Tab /
SFSafariViewController zijn: url_launcher met
LaunchMode.inAppBrowserView (6.3.1 staat al in pubspec) — niet
inAppWebView, dat is wél een WebView.sessid/session_name
als losse App State-waarden en plakt zelf Cookie: … op elke call. Een
browser-login levert dus niks op tenzij iets die sessie als waarde
teruggeeft. Dat is het hele bestaansrecht van het claim-endpoint hieronder.De keten: app maakt random state → opent Custom Tab → gebruiker logt in bij
Facebook → OneAll levert connection_token bij de callback → callback wisselt
'm in bij de OneAll API, logt in met user_login_finalize(), bewaart
sessid+session_name onder die state (cache, TTL 5 min, eenmalig) → app pollt
het claim-endpoint elke 2 s → krijgt de sessie → closeInAppWebView().
Bewuste keuze: pollen, geen deep link. Het schema
uitgaanskrant://uitgaanskrant.com bestaat en werkt, maar flutter_web_auth_2
vereist een eigen activity in AndroidManifest.xml en de export overschrijft
dat bestand. Pollen + closeInAppWebView() kost geen package, geen manifest en
geen go_router-route.
Voordeel dat je moet kennen: er hoeft géén aparte Facebook-app geregistreerd te worden. Alle OAuth-redirects lopen naar oneall.com; Facebook kent alleen OneAll als client. Er verandert dus niets aan de Facebook-instellingen van de site.
| # | Wie | Wat |
|---|---|---|
| 130-A | Bob | Upgrade naar Personal Advanced + controleer dat uitgaanskrant.com bij de allowed domains staat |
| 130-B | ✅ Besloten 2026-09-21 | Automatisch account aanmaken. De callback maakt zelf een Drupal-account op het geverifieerde e-mailadres van het netwerk; zonder geverifieerd e-mailadres weigeren we. Gebruikersnaam afleiden en op uniciteit controleren. registration_method = manual in de module blijft ongemoeid — dat geldt alleen voor de website |
| 130-C | ✅ Besloten 2026-09-21 | Geen iOS op korte termijn → Apple-login overslaan. Play kent geen tegenhanger van richtlijn 4.8. De provider is een parameter in de URL, dus later bijzetten raakt de app-kant niet. Blijft open punt zodra iOS aan de beurt komt |
| 130-D | ✅ Code klaar (2026-09-21), Bob deployt | custom.social_login_app.inc — callback die de token inwisselt (hergebruikt social_login_core_get_settings() / _do_api_request() / _get_user_for_user_token() / _get_uid_for_email() / _map_identity_token_to_user_token()), inlogt met user_login_finalize() en de sessie onder de state in de cache-bin legt (TTL 5 min, eenmalig). php -l schoon op PHP 8.1. Staat in ~/tmp/ op aegir-o1 |
| 130-E | ✅ Code klaar (2026-09-21), Bob deployt | POST /flutterdrup/app_social_login/claim.json → {status: pending\|ok\|error}; bij ok staan sessid/session_name/token/user erin als bij user/login.json, maar met een klein user-object (uid/name/mail) — geen volledig object in een anoniem claimbaar antwoord. Access callback _custom_social_app_claim_access() (TRUE, huispatroon van custom_plaatsen_access) |
| 130-F | 🟡 Werkt op devbob (2026-09-21) | 4/4 tests groen: callback rendert, callback→claim vinden elkaar via de cache, een state is eenmalig, ongeldige state geeft 400. Nog te doen: de bijgewerkte .inc + de tweede hook_menu-regel (pad-variant) plaatsen, en daarna alles naar productie — de app praat met uitgaanskrant.com |
| 130-G | ✅ Code klaar (2026-09-21), nog niet in de builder | socialLoginStart(provider) — state genereren, Custom Tab openen (LaunchMode.inAppBrowserView, niet inAppWebView), elke 2 s claimen, App State vullen als drupalLogin, tab sluiten met closeInAppWebView(). Timeout 3 min. dart analyze 0 errors. Wacht op 130-F. ⚠️ Te verifiëren op een toestel: of Android de polling niet throttlet terwijl de Custom Tab voorgrond is — zo ja, claimt hij alsnog bij terugkeer, want de state is 5 min geldig |
| 130-H | 📋 Draaiboek klaar (2026-09-21) | Twee knoppen onder de bestaande Inloggen-knop, gemaakt door die knop te dupliceren (erft stijl). Facebook #1877F2/wit, Google wit met rand #DADCE0. Per knop een unieke Action Output Variable Name, anders dubbele declaratie in het model. Stappen in 130-app-draaiboek.md |
| 130-I | 📋 Teksten klaar (2026-09-21) | Log in with Facebook / Log in with Google, via het globe-icoontje. ⚠️ Een gedupliceerde knop erft de Engelse vertaling van het origineel, dus dit hoort bij 130-H, niet erna |
| 130-J | Bob | Echte doorloop met een Facebook- én een Google-account op een profile-build. Claude kan dit niet — geen wachtwoorden |
| 130-K | ✅ Tekst klaar (2026-09-21), Bob voert door | Drie wijzigingen in /nl/support/privacybeleid (sectie 1a account, sectie 3 derden met OneAll+Meta+Google, datum/draft) + in Data Safety komt Personal info → Name erbij als Collected. Volledige tekst in 130-K-privacy.md. ⚠️ Losse bug daarbij gevonden: de privacylink in de cookiebanner geeft 404 (/nl/support/algvoorwaarden, moet /nl/support/algemenevoorwaarden zijn) — Play eist een werkende privacy-URL, dus dit is sowieso repareren op /admin/config/system/eu-cookie-compliance |
| 130-L | Claude | Bevindingen naar CLAUDE.md |
⏰ HERINNERING VOOR 2026-09-22 (Bob vroeg hier expliciet om):
devbob.uitgaanskrant.com toevoegen aan de allowed domains in het
OneAll-dashboard. Kon op 2026-09-21 niet — devbob had een storing. Zonder dat
domein weigert OneAll de callback_uri en is testen op devbob onmogelijk.
Stand 2026-09-21, tweede testronde — backend is KLAAR:
Beide callback-routes bewezen op devbob: /callback?state=X én
/callback/X, en in beide gevallen vindt de claim de state terug
({"status":"error","message":"cancelled"}), geeft een tweede claim weer
pending en een te korte state een 400. Er valt aan de Drupal-kant niets meer
te testen tot het OneAll-abonnement omgaat. Rest: de aangepaste .inc +
het tweede hook_menu-item nog naar productie (de oude versie staat er al).
Stand 2026-09-21 na de eerste testronde:
uitgaanskrantcom) als
productie. Testen op devbob kan dus, maar dan moet devbob.uitgaanskrant.com
bij de allowed domains staan (Advanced geeft er 8)./nl/app-social-login/callback/<state>). Het is niet bewezen dat OneAll een
bestaande query-parameter in de callback_uri laat staan als hij er zelf
?connection_token= aan toevoegt; met de state in het pad kan dat niet
misgaan. De query-variant blijft bestaan als terugval en om handmatig te
testen — daarom staan er twee hook_menu-items.Alles wat zonder het abonnement kon, is af (2026-09-21). Backend getest, app-code geschreven, privacytekst klaar, bouwdraaiboek klaar. Openstaand tot de upgrade: alleen 130-A, en daarna G t/m J in één sessie.
⚠️ Besluit Bob 2026-09-21: Claude werkt pas in FlutterFlow zodra het abonnement is aangepast. Tot die tijd alleen voorbereiden.
💡 De app-keten is te testen zonder de upgrade: vervang in
socialLoginStart de startUrl tijdelijk door callbackUri, dan slaat hij
OneAll over en loopt de rest van de keten wél (browser, polling, foutafhandeling,
tab sluiten). Zie het draaiboek.
Volgorde: A t/m C zijn besluiten en blokkeren D. F blokkeert G. K moet af vóór de Play-inzending als 130 in de eerste release meegaat.