# TASKS — alleen nog OPEN taken (opgeschoond 2026-09-19) *Deze lijst is op 2026-09-19 teruggebracht van 6676 naar een paar honderd regels: alle afgeronde taken en sessieverslagen zijn eruit gehaald. Ze staan in de git-historie (commit `92225d0` is de laatste versie met alles erin) en de blijvende inzichten staan in `CLAUDE.md`. **Alles hieronder is open werk.** Staat iets er niet meer in, dan is het klaar of afgewezen — niet opnieuw agenderen.* **Voor een nieuwe sessie:** pak een taak uit "Claude — open" hieronder, zet er **meteen** `— bezig` achter in dit bestand (een echte `Edit`, niet pas bij het afsluiten; andere sessies delen deze working directory), en verifieer elke builder-wijziging met een verse export. Taken onder "Bob — open" niet zelf oppakken. --- ## 👤 CLAUDE — OPEN | # | Taak | Kort | |---|---|---| | ~~88B~~ | Pagina "Mijn aanmeldingen" | ⚠️ **Al geclaimd** door de sessie 'taken-B1-B7' — niet oppakken | | **98** | "Zet in mijn agenda"-knop op een evenement (`add_2_calendar`) | Claude — **BEZIG** (2026-09-19). 98A en 98B zijn af; alleen de knop op `EventCurrent` rest nog | | **65** | Release-build doorloop | ✅ **Uitgelogde deel af 2026-09-19** (0 crashes; 65a, 65b en 65d-kleur opgelost; 65c + 65e open). Rest = ingelogd + Xiaomi → Bob | | **P2-24 B** | Categoriedropdown horeca-overzicht, tab-bewust | Wacht op Bob's access-callback (zie P2-24 B); custom functions staan klaar | | **G0.6** | Play-screenshots schieten | Browserloos op beide AVD's, ±10 min — zeg het en het gebeurt | ## 👤 BOB — OPEN, in volgorde van wat de livegang blokkeert | # | Taak | Waarom nu | |---|---|---| | ~~B1~~ | App-icoon | 🟢 **Niet meer blokkerend** (gemeten Claude 2026-09-19). De export wijst al naar PNG's en het logo staat op **64 %** van de breedte: "Uitgaanskrant.com" is volledig leesbaar, "itgaanskrant.co" is weg. Rest is cosmetisch — zie het B1-blok | | ~~B1b~~ | Splashscreen | ✅ **AF** (Bob 2026-09-19, export-geverifieerd): `nav.dart` toont `logo_1080x2160.png`; op telefoon, 9:20, tablet staand én liggend volledig leesbaar | | **G** | Google Play-checklist G0–G4 | Volledige checklist hieronder, afvinkbaar | | **66** | Crashlytics: Firebase-console nakijken + Crashtest-knop opruimen | App-kant is bewezen (rapport met status 200); alleen de console-check en het opruimen zijn nog van jou | | **P2-24 B** | `horecacategorieen` publiek: **devbob ✅, nog naar productie** | Werkt en is nagemeten op devbob. Na deploy: `cc all`, resource aanvinken op het `flutterdrup`-endpoint, anoniem testen, Cloudflare 2-uurs-rule | | **P1-17** | Engelse vertalingen, één ronde | 46 velden op de twee aanmaakpagina's + 3 losse | | **Bij livegang** | Zes schakelaars en opruimklussen | Eigen blok hieronder | | **Drupal** | taak 56, P2-25 | Eigen blokken hieronder (P1-42/P1-43 zijn af, nagemeten 2026-09-19) | --- # ### 🆕 88 · "Mijn aanmeldingen" + kloonknop · deel A ✅ AF · **deel B: Claude — BEZIG (sessie 'taken-B1-B7', 2026-09-19)** **📍 Voortgang deel B (2026-09-19, sessie 'taken-B1-B7'):** 1. ✅ **API Call `MijnAanmeldingen`** aangemaakt en export-geverifieerd — `sessionName`/`sessid` (String) + `page`/`limit` (Integer, default 0 en 50), header `Cookie: '${sessionName}=${sessid}'` (echte interpolatie), query-params `page`+`limit` From Variable, `cache: false`. 2. ✅ **Pagina `MijnAanmeldingen`** aangemaakt (blanco), route `/mijnAanmeldingen`. 3. 🔵 **AppBar toegevoegd** aan de Scaffold — eigenschappen nog te zetten volgens het header-recept in `CLAUDE.md` (Info / Elevation 2 / Show Default Button uit / 80 PX / Toolbar 25 % / Bottom Section / Center Title / Flexible Space / Background Widget → `HeaderButtonsComponent` met `showBackButton` AAN). 4. ⬜ Drawer + `drawerComponent` 5. ⬜ StaggeredView 1/1/2/3 + Backend Query + Generate Dynamic Children (⚠️ JSON Path `$`, **niet** Predefined Path — gepagineerde lijst, zie `CLAUDE.md`) 6. ⬜ Kaart: titel, datum, statuslabel, knop *Bekijken* 7. ⬜ Ingang op Favorieten tab 4 (Ctrl+D op een bestaande rij, binnen de login-guard) ⚠️ **Deel B is geclaimd — niet ook oppakken.** Deel A (beide Drupal-endpoints) draait op **productie**, getest 2026-09-19: A1 geeft 122 aanmeldingen voor `bobhoreca`, A2 geeft 200 op eigen nodes en **403 op andermans** (bevestigd op vier vreemde nids). `?limit=` en `?page=` werken allebei. ⚠️ **Default limit is 50** — de app-lijst moet dus pagineren. Bob 2026-09-18: *"Ziet een horecagelegenheid-eigenaar of stadsrechten-eigenaar ook een overzicht van hun zelf aangemaakte dingen? En ik wil er een knop achter, klone, waardoor het formulier met de eerdere gegevens wordt gevuld."* **Antwoord op de eerste vraag: nee, dat bestaat nog niet.** Gemeten 2026-09-18 op de export: de enige "mijn"-endpoints zijn `mijn_horecagelegenheden.json` (vult de dropdown op `uitgaansevenementAanmaken`) en `mijn_stadsrechten.json`. Er is geen enkele call die teruggeeft *wat deze gebruiker heeft aangemaakt*. Na het indienen van een evenement verdwijnt het dus uit beeld: de gebruiker kan niet zien of het erop staat, niet of het al goedgekeurd is, en niets hergebruiken. **A · Drupal (Bob) — twee endpoints, allebei sessie-geauthenticeerd** - **A1 · `mijn_aanmeldingen.json`** (zelfde auth-patroon als `mijn_horecagelegenheden.json`). Alle nodes van type `go_out_event` **en** `activity` met `uid` = huidige gebruiker, nieuwste eerst. Per rij: `nid`, `type`, `title`, `datum_start` (rauw `yyyy-MM-dd HH:mm:ss`), `status` (0/1), `plaats`, `logo` (URL, voor een thumbnail). - **A2 · `aanmelding_kloonbron.json?nid=X`** — sessie-auth **plus een eigenaarscheck** (`$node->uid == $user->uid`, anders 403; zonder die check kan iemand andermans gegevens uitlezen). Levert de **rauwe** waarden, niet de weergavewaarden: `horecagelegenheid_nid`, `titel`, `omschrijving` (rauwe body), `entree_tid`, `entreeprijs`, `toelichting_entree`, `tickets_url`, `website_url`, `categorie_tids` (lijst), `logo_fid`, `fotos_fids` (lijst). *Waarom A2 een eigen endpoint moet zijn en `flutterflow_events.json` niet volstaat — gemeten op nid 214439:* die view geeft `categorie` als **labels** (`['Tweedehands markt']`) i.p.v. tids, `logo`/`fotos` als **URL's** i.p.v. fids, `datum` als `zaterdag 19 sep, 9:00` (geen jaar, niet parseerbaar), `website` verpakt in een ``-tag, en er zit **geen entree-tid** in. Daar is geen formulier mee te vullen. **✅ Besluit Bob 2026-09-19: een kloon hergebruikt dezelfde `fid`'s.** De gebruiker hoeft dus niet opnieuw te uploaden. Aan de Drupal-kant regelt `file_field_insert()`/`file_field_update()` de `file_usage`-registratie bij `node_save()` zelf zodra de fids op het veld staan; alleen controleren dat `_custom_evenement_create()` bestaande (permanente) fids accepteert en niet op `status = 0` filtert. **B · App (Claude, builder)** - **B1 · Nieuwe pagina `MijnAanmeldingen`** met de standaard header + Drawer (recept staat in `CLAUDE.md`), een `StaggeredView` over A1 met Cross Axis Count 1/1/2/3 en een Empty List Widget. Per kaart: titel, datum, **statuslabel** ("Gepubliceerd" bij `status: 1`, "Wacht op goedkeuring" bij `0`) en twee knoppen: *Bekijken* (Navigate → `EventCurrent` met `nid`) en *Klonen*. - **B2 · Ingang:** Favorieten tab 4 (Gebruiker), waar *Uitgaansevenement aanmaken* en *Stadsactiviteit aanmaken* al staan. Eén `ListTile` erbij, **binnen** de bestaande `userSessionid`-login-guard. Dupliceer een bestaande rij (Ctrl+D) — dat is hier het bewezen patroon, Insert Widget niet. - **B3 · Klonen.** De kloonknop haalt A2 op in zijn eigen actieketen (Backend Call) en geeft de waarden mee als **page parameters** aan de Navigate To naar `uitgaansevenementAanmaken`. **Niet** via een page state die je in On Page Load vult: `Initial Option Value` van een dropdown wordt tijdens de eerste build geëvalueerd en On Page Load draait pas in een `addPostFrameCallback` — dan is de `FormFieldController` al met de lege waarde geïnitialiseerd (staat zo in `CLAUDE.md`). Nieuwe parameters op `uitgaansevenementAanmaken`, **allemaal niet-verplicht** (een verplichte parameter genereert een `!` en laat de gewone "nieuw"-route crashen): `kloonHorecaNid`, `kloonTitel`, `kloonOmschrijving`, `kloonEntreeTid`, `kloonEntreeprijs`, `kloonToelichting`, `kloonTicketsUrl`, `kloonWebsiteUrl` (String), `kloonCategorieTids` + `kloonFotosFids` (List), `kloonLogoFid`. Binden: elke `TextField` → *Initial Value*, elke dropdown → *Initial Option Value*. **Bewust NIET klonen: de twee datumvelden.** Je kloont juist om hetzelfde evenement op een níeuwe datum te zetten, dus een overgenomen datum is per definitie fout en moet alsnog weg. Twee lege datumvelden zijn meteen ook de rem tegen per ongeluk twee keer exact dezelfde aanmelding publiceren. **Volgorde.** A1 + B1 + B2 (het overzicht) is op zichzelf al waardevol en veel kleiner dan de kloonknop — lever dat als eerste op, A2 + B3 daarna. Zonder het overzicht heeft de kloonknop trouwens geen plek om te staan. **Twee valkuilen vooraf.** (1) De `List`-parameters zijn het fragiele deel: een List-typed argument dat op `Unset` blijft staan blokkeert de héle export met een generieke 400 (zie `CLAUDE.md`) — loopt dat vast, dan is het een Bob-stukje. (2) `stadsactiviteitAanmaken` heeft exact hetzelfde formulier­patroon; bouw eerst evenementen helemaal af en kopieer daarna pas, niet allebei tegelijk. --- ### 🆕 98 · "Zet in mijn agenda"-knop op een evenement · Claude — **BEZIG** (2026-09-19) Knop op `EventCurrent` die het evenement in de agenda van de telefoon zet, via het pub-pakket **`add_2_calendar` 3.1.1** (160/160 pub points, 364 likes, 134k downloads/maand, MIT). Zelf bouwen kan niet: iOS vereist EventKit in Swift via een platform channel, en dat kan FlutterFlow's Custom Code niet. **Blokkeert op 98A** — `flutterflow_events` levert alleen `datum` als Nederlandse weergavestring (`zaterdag 19 sep, 9:00`): geen jaartal, geen eindtijd. Gemeten op nid 214439, 2026-09-19. Stappen: 1. **98A** (Bob, Drupal) — ISO start/eind aan de view toevoegen. 2. ✅ **iOS-permissie uitgezocht (Claude, 2026-09-19): FlutterFlow heeft gewoon een "Calendar"-rij** in App Settings → Permissions, dus de plist is te regelen. ⚠️ **Maar het tekstveld ernaast is voor Claude onbruikbaar** — het slaat elke toetsaanslag op als eigen plist-entry (69 dubbele keys in de export, breekt de iOS-build). Zie de nieuwe notitie in `CLAUDE.md`. Het project is weer schoon achtergelaten (permissie uit, 0 keys). 👉 **Overgedragen aan Bob — zie 98B.** Android heeft niets nodig. 3. ✅ **Dependency staat erin (Claude, 2026-09-19): `add_2_calendar: ^3.0.1`** via App Settings → Project Dependencies → Custom Pub Dependencies. ⚠️ **Niet ^3.1.1**: versies vanaf 3.1.0 eisen Dart >= 3.11, en dit project draait Flutter 3.35.7 / Dart 3.9.2. `pub get` lost 3.0.1 op. 4. ✅ **Custom action `zetInAgenda` staat er** (Claude, 2026-09-19), `Future`, 5 argumenten (`titel`, `startIso`, `eindIso?`, `locatie?`, `omschrijving?`). Doet: ISO parsen, eindtijd = start + 2 uur als er geen is, HTML uit de omschrijving strippen en afkappen op 500 tekens, lege titel → "Evenement". Geverifieerd op een verse export: **0 analyse-errors** tegen het echte pakket. 5. ✅ **Knop staat op `EventCurrent`** (Claude, 2026-09-19) — naast de bestaande deel-knop in de titel-`Row`, gemaakt door die `Tooltip > IconButton` te **dupliceren** (stijl en tooltip komen dan gratis mee). Icoon `Icons.event_available`, tooltip "Zet in mijn agenda". Bindingen, alle vijf geverifieerd op een verse export: `titel` -> `$[0].title` · `startIso` -> `$[0].datum_iso` · `eindIso` -> leeg · `locatie` -> `$[0].adres` · `omschrijving` -> `$[0].body` · `plaats` -> `$[0].plaats`. **Bob's besluiten 2026-09-19: GEEN eindtijd** (de action zet `eind = start`, zodat de agenda-app zelf zijn standaardduur kiest i.p.v. dat wij 2 uur verzinnen) en **locatie = adres + plaats** (de action voegt ze samen en slaat een leeg deel over, dus nooit een losse komma). **Geen Visibility-conditie** — gemeten op 300 productie-events: `datum_iso` en de andere velden zijn er altijd, dus de knop werkt altijd; een conditie zou alleen het P2-44-risico (bool-cast zonder null-check) introduceren. 6. ⏳ **Laatste stap: één tik op een ECHT toestel** (Bob) — op beide AVD's niet te testen, zie hieronder. **Wat wél bewezen is (profile-build, emulator-5554, 2026-09-19):** de knop rendert, een tik roept de action aan en die vuurt de juiste intent af (`act=android.intent.action.INSERT dat=content://com.android.calendar/…`). De app zelf geeft geen enkele fout en komt netjes terug op de voorgrond. ⚠️ **Wat de AVD's niet kunnen:** hun AOSP-kalender is een kapotte stub — het manifest declareert `com.android.calendar/.EditEventActivity` mét de INSERT-filter (dus Android routeert de intent erheen), maar de klasse zit niet in de APK: `ClassNotFoundException`, de kalender-app crasht. Geldt voor **beide** AVD's (`cmd package resolve-activity` geeft op allebei "No activity found"). Emulator-defect, geen app-bug — maar de voorgevulde agenda-dialoog is daardoor alleen op een echt toestel te zien. ### ✅ 98B · Calendar-permissietekst — AF (Bob, 2026-09-19) Verse export geeft precies 3 `UsageDescription`-keys, één `NSCalendarsUsageDescription` met de volledige zin. **Belangrijk nevenresultaat: het per-toetsaanslag-probleem dat Claude tegenkwam is Claude-specifiek, niet een FlutterFlow-bug** — zie de bijgewerkte notitie in `CLAUDE.md`.
oorspronkelijke opdracht App Settings → Permissions → rij **Calendar**: toggle aan en als bericht `Deze app vraagt toegang tot je agenda om een evenement toe te voegen.` Controleer daarna op een **verse export**: `grep -c "UsageDescription" ios/Runner/Info.plist` → moet **3** geven (Camera, PhotoLibrary, Calendar). Geeft het er tientallen, dan heeft jouw browser hetzelfde per-toetsaanslag-probleem als Claude's; zet de toggle dan weer uit (dat ruimt alles op) en meld het.
### ✅ 98A · ISO-datum aan `flutterflow_events` — AF, STAAT OP PRODUCTIE (2026-09-19) Geverifieerd op productie mét én zonder cache-buster (dus cache geleegd): nid 214439 → `datum_iso: "2026-09-19 09:00"`, nid 222813 → `"2026-12-10 20:30"`. Repeat rule staat op *Hide*. Onderstaande devbob-meting blijft als naslag. Bob heeft veld `field_date_1` toegevoegd met label **`datum_iso`**, formatter Date and time → *custom*, format `Y-m-d H:i:s`, Display *Start date only*. **Geen eindtijd** (Bob's besluit 2026-09-19) — de custom action maakt er start + 2 uur van. **Getest door Claude op devbob (2026-09-19), 1200 unieke events gescand:** 1200 schoon parseerbaar, 0 leeg, 0 afwijkend; de tijd komt exact overeen met het bestaande `datum`-veld, dus de tijdzone klopt. - ⚠️ **Let op: de output heeft GEEN seconden** (`2026-09-19 09:00`), ondanks `H:i:s` in het format — de Date-module strippt `s` omdat de granularity van `field_date` geen seconden kent. **Geen probleem:** `DateTime.tryParse` in `zetInAgenda` accepteert dat, en ook mét seconden en milliseconden (alle vier getest). Een leeg veld én het oude `datum`-formaat worden netjes geweigerd (`false`, geen crash). - ⚠️ **Restpuntje voor Bob:** het nieuwe veld staat op `'show_repeat_rule' => 'show'`, het bestaande `datum`-veld op `'hide'`. Bij een event mét herhaalregel plakt Date die tekst achter de waarde en is `datum_iso` onparseerbaar (gevolg is mild: knop doet niets). Geen van de 1200 events heeft er nu een. **Zet 'm bij de productie-ronde op "Hide repeat rule".** **Nog te doen: naar productie**, daarna `drush cc all` **én** de Cloudflare-cache purgen (dit endpoint zit in de 5-minuten Cache Rule). ### 🔧 Kleine open restpunten (app) *(P2-45b, P2-45c en 76 zijn op 2026-09-19 afgerond en geverifieerd met een verse export — zie de commit van die datum.)* --- ## 🅖 G · GOOGLE PLAY — volledige checklist · Eigenaar: Bob *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:** - **Een organisatie-account is vrijgesteld van de 12-testers/14-dagen-regel.** Die geldt alleen voor *persoonlijke* accounts van ná 13-11-2023. De keuze voor Organisatie was dus juist en scheelt minimaal twee weken. - **D-U-N-S duurt tot 30 dagen.** Dat is de kritieke pad-stap, niet de $25. - **Legal name + adres in het Google Payments-profiel moeten EXACT matchen met het D-U-N-S-profiel.** Mismatch = e-mail met deadline, daarna verwijdering van de app uit Play. Dit is de meest gemaakte fout en kost weken. # ### G0 · Nu doen, terwijl de verificatie loopt (buiten Play Console) - [x] **G0.1 — D-U-N-S: AL BINNEN** (Bob 2026-09-19: "die krijg je standaard in Nederland, heb ik al gevonden"). De langste wachtstap valt dus weg. ⚠️ Blijft staan: leg het nummer vast mét de **exact** daarop geregistreerde bedrijfsnaam en adres — die moeten letterlijk matchen met G0.2. - [ ] **G0.2 — Payments-profiel gelijktrekken** met precies die naam/adres. - [ ] **G0.3 — Besluit upload-route** (was taak 63d, nog open): FlutterFlow-deploy (advies — dan hoeft er lokaal niets) óf lokaal. Lokaal betekent een keystore + `android/key.properties`, want `android/app/build.gradle:80` staat nog op `signingConfig signingConfigs.debug` — daarmee kun je niet uploaden. - [ ] **G0.4 — Besluit iOS wel/niet** (was taak 63e). Er is niets voor ingericht; alleen `project.pbxproj` beweegt mee in elke export. - [ ] **G0.5 — Teksten schrijven:** korte omschrijving (max 80 tekens) + lange (max 4000). Basis = de helptekst van taak 59. - [ ] **G0.6 — Screenshots laten maken.** Claude schiet ze browserloos op beide AVD's (telefoon + tablet), ±10 min. Nodig: 2+ telefoon, én 7" en 10" tablet. - [ ] **G0.7 — Feature graphic 1024×500** laten maken (grafisch werk, Bob). # ### G1 · Zodra het account geverifieerd is - [ ] **G1.1 — Create app.** Naam "Uitgaanskrant.com", Nederlands, App (geen game), Gratis. Package is `com.uitgaanskrant.app` — **onveranderlijk na de eerste upload**, dus controleer 'm. - [ ] **G1.2 — Payments-profiel verifiëren** (deposit-challenge of bankdocument, ±5 dagen). Moet vóór publicatie rond zijn. # ### G2 · App content (Policy and programs → App content) - [ ] **G2.1 — Privacy policy-URL:** `https://uitgaanskrant.com/nl/support/privacybeleid` (anoniem 200, gemeten 2026-09-16). - [ ] **G2.2 — Data deletion-URL:** `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. - [ ] **G2.3 — App access:** "All or some functionality is restricted" + de inloggegevens van het testaccount (taak 86, al aangemaakt). ⚠️ Wachtwoord **niet** in dit bestand — alleen Play Console + wachtwoordmanager. Zorg dat er 3-4 favorieten in Amsterdam en één gevolgde gemeente op staan, anders ziet de reviewer vier lege tabs en leest dat als kapot. - [ ] **G2.4 — Ads:** *Contains ads* → **JA** (AdMob zit in de build). - [ ] **G2.5 — Content rating:** IARC-vragenlijst invullen. - [ ] **G2.6 — Target audience:** bewust kiezen — er staat 18+ uitgaanscontent in. - [ ] **G2.7 — Data safety.** Gemeten in het manifest van de release-APK: permissies `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. - [ ] **G2.8 — Government apps / Financial features:** beide nee (staat er wel, makkelijk over te slaan, blokkeert anders de release). # ### G3 · Store listing - [ ] **G3.1 — App-icoon 512×512.** Gebruik `assets/images/app_launcher_icon.png` (het volvlakse), **niet** de adaptive foreground. - [ ] **G3.2 — Feature graphic 1024×500** (uit G0.7). - [ ] **G3.3 — Screenshots** telefoon + 7" + 10" tablet (uit G0.6). Tabletshots zijn verplicht omdat de app tablets ondersteunt. - [ ] **G3.4 — Korte + lange omschrijving** (uit G0.5). - [ ] **G3.5 — Categorie + contactgegevens + e-mail.** Die e-mail wordt **publiek** getoond. # ### G4 · Release - [ ] **G4.1 — versionCode ophogen** vóór élke upload. Staat nu op `1.0.0+1` (`pubspec.yaml:18`). Bij de FlutterFlow-route: App Settings → App Details. - [ ] **G4.2 — Build uploaden** volgens de route uit G0.3. AAB, niet APK — die splitst per architectuur (de APK is nu 95,6 MB). - [ ] **G4.3 — ⚠️ Icoon controleren op het geüploade artefact zelf.** Open vraag: doet FlutterFlow's deploy-pipeline `flutter_launcher_icons` wél? Lokaal gebouwd draagt het bestand anders het Flutter-vogeltje. Niet aannemen. - [ ] **G4.4 — Interne test eerst** (geen tester-minimum voor organisaties), dan pas productie. ### ✅ 88A · Drupal-endpoints — AF, staan op productie (2026-09-19) `mijn_aanmeldingen.json` en `aanmelding_kloonbron.json` zijn geplaatst en getest. Details staan in het 88-blok hierboven; deze taak is klaar. ### ✅ 66 · Crashlytics — APP-KANT BEWEZEN 2026-09-19 (Claude). Rest: Bob kijkt in de console. **De keten werkt, end-to-end gemeten** op een profile-build op emulator-5554: tik op *Crashtest* → `FirebaseCrashlyticsTestCrash` in logcat, proces weg → app opnieuw geopend → `Making request to crashlyticsreports-pa.googleapis.com` → **`Status Code: 200`**. Het rapport is dus daadwerkelijk verstuurd. 🔴 **En meteen de verklaring waarom er nooit een crash in het dashboard stond: Bob's netwerk-DNS (192.168.8.220) blokkeert `crashlyticsreports-pa.googleapis.com` (antwoordt `0.0.0.0`).** Elk rapport vanaf dit netwerk verdween in een sinkhole. De koppeling was al die tijd in orde. Details + de omzeiling staan in `CLAUDE.md`. `emulator-5554` staat nu op private DNS (`dns.google`) zodat Crashlytics daar werkt — terugzetten met `adb shell settings put global private_dns_mode off`. **Voor Bob, in Firebase:** zie het antwoord in de chat — kort: Crashlytics openen, wachten tot de eerste crash het dashboard "activeert", en controleren dat het `bundle_id` `com.uitgaanskrant.app` is. Daarna de Crashtest-knop weer weghalen. ⚠️ **Bob's huidige knop hangt aan de UITLOG-keten.** De `Duplicate` nam alle zeven uitlog-acties mee en `crashlyticsTest` staat er als achtste achter; je wordt dus eerst uitgelogd en naar Login gestuurd, en pas daarna crasht hij. Het werkt, maar voor een schone stack trace: Actions → Edit → acties 1 t/m 7 verwijderen (⋮ → Delete Action). **Nog open (los van deze taak):** `main.dart:44` zet alléén `FlutterError.onError`. Er is **geen** `PlatformDispatcher.instance.onError`, dus een fout in een async custom action buiten het framework wordt niet gerapporteerd. Eén regel, maar `main.dart` is gegenereerd — moet via FlutterFlow's Final Actions/custom-code-hook. ### (origineel) 66 · Crashlytics-testactie · Eigenaar: Bob (plaatsen + Firebase bekijken) `crashlytics_test.dart` — één custom action `crashlyticsTest(String? modus)`, argument mag **Unset** blijven (String mag dat; alleen een List-typed argument blokkeert de export). Drie standen: Unset → echte crash, `melding` → niet-fatale melding, `flutter` → via de `FlutterError.onError`-koppeling uit `main.dart`. **Gemeten gat in de huidige opzet:** `main.dart:44` zet alléén `FlutterError.onError`. Er is **geen** `PlatformDispatcher.instance.onError`, dus een fout in een async custom action buiten het framework wordt **niet** gerapporteerd. Zeg het als dat erbij moet — dat is één regel in `main.dart`, maar `main.dart` is gegenereerd, dus het moet via FlutterFlow's *Final Actions*/custom-code-hook, niet lokaal. **Verwachting bij het testen:** een echte crash wordt pas bij de **volgende start** van de app geüpload. Een `melding` gaat meteen weg en verschijnt onder *Non-fatals*. Reken op ~5 minuten tot het in het dashboard staat. ### 65 · Release-build — bevindingen en wat er nog getest moet worden · Eigenaar: Claude (doorloop) + Bob (icoon) Eerste echte `--release`-build ooit, uit een verse export mét `--include-assets` (`/tmp/ff-rel`, `fvm use 3.35.7 -f`, `dart analyze` **0 errors**), geïnstalleerd op **emulator-5556** (telefoon; 5554 was bezet door de sessie van taak 89). `dumpsys` bevestigt `flags=0x0` — geen `DEBUGGABLE`, dus een echte release. **✅ Wat goed gaat.** De app start schoon op: **0 `FATAL`, 0 `E/flutter`** in logcat over de hele sessie. Data laadt (Amsterdam, slider, kaarten met foto's), Nederlandse locale klopt, zoekveld + datumchips + vijf tabs staan er, en **alle iconen renderen** — óók het enige FontAwesome-icoon (`flagCheckered` bij *Activiteiten* in het menu), dat de tree-shaking dus overleeft. Het tree-shaking-risico uit de oorspronkelijke taakomschrijving is hiermee van tafel: er zijn **0 dynamisch opgebouwde `IconData(`** in de hele levende code, en `fa-solid-900.ttf` krimpt netjes van 419 KB naar 1248 bytes. Screenshots: `scratchpad/rel-home.png`, `rel-menu.png`. **🔴 BLOKKEREND — een lokaal gebouwde release draagt het Flutter-standaardicoon.** Het blauwe Flutter-vogeltje, niet het rode merkicoon. Hard aangetoond, niet afgeleid: het icoon uit `app-release.apk` is pixel-identiek aan de Flutter-default (68 % `#40c0e0`, 31 % dekking) terwijl `app_launcher_icon.jpg` 83 % `#a00000` en 100 % dekking heeft; visueel bevestigd, het is letterlijk het Flutter-logo. *Oorzaak.* Een **verse export** heeft `android:icon="@mipmap/ic_launcher"`, alleen de default `ic_launcher.png` (md5 `57838d52…`), **geen** `mipmap-anydpi-v26/` en **geen** `values/colors.xml`. Het blok `flutter_launcher_icons:` in `pubspec.yaml` is wél correct ingevuld (`image_path`, `adaptive_icon_background: '#9a141d'`), maar **niemand voert die generator uit** — `flutter build` doet dat niet. *Fix, één commando in de exportmap vóór het bouwen:* ``` fvm flutter pub run flutter_launcher_icons ``` Dat herschrijft het manifest naar `@mipmap/launcher_icon`, genereert de merkiconen, `mipmap-anydpi-v26/launcher_icon.xml` en `colors.xml`. Na die stap opnieuw gebouwd en geverifieerd: `aapt2 dump badging` geeft dan `application-icon-640:'res/IO.xml'` (een adaptive icon) i.p.v. een PNG. ⚠️ **Open vraag die iemand met Play-toegang moet beantwoorden:** doet FlutterFlow's eigen Deploy-pipeline (de AAB die je daar downloadt) deze stap wél? Waarschijnlijk, maar niet te meten vanaf hier. **Controleer het icoon dus op het artefact dat je daadwerkelijk uploadt**, niet op dit lokale experiment. Bouw je lokaal, dan is de generator-stap verplicht. **🟠 Het adaptive icon snijdt de merknaam af — "itgaanskrant.co".** Ook ná de generator. `adaptive_icon_foreground` wijst naar een **JPEG** (1408×1408), en een JPEG kan geen transparantie hebben: de gegenereerde `drawable-xxxhdpi/ic_launcher_foreground.png` is 432×432 met **0 % transparante pixels** en de buitenste 17 % is **100 % ondoorzichtig**. Android toont van een foreground alleen de middelste ~66 % en maskeert die rond. Gevolg: de achtergrondkleur `#9a141d` is nooit zichtbaar (de foreground dekt alles af) en de witte pil met de naam loopt aan beide kanten van het icoon af — de **U** en de **m** vallen weg. Gerenderde preview: `scratchpad/adaptive-zoals-android-toont.png` naast `icoon-bron.png`. *Fix:* lever als `adaptive_icon_foreground` een **PNG met transparantie** waarin alleen het beeldmerk op ±60 % van het vlak staat, gecentreerd; de rest transparant. `image_path` (het gewone icoon) mag het huidige volvlakse plaatje blijven. Hoort bij dezelfde ronde als taak 79. **Kleine waarnemingen, geen actie nodig tenzij je wilt:** - APK is **95,6 MB**. Voor Play upload je een AAB, die splitst per architectuur en wordt fors kleiner — maar het is groot genoeg om even naar te kijken. - Het manifest van de gebouwde app bevat naast de vier eigen permissies ook `ACCESS_NETWORK_STATE`, `WAKE_LOCK`, `FOREGROUND_SERVICE`, `AD_ID` en drie `ACCESS_ADSERVICES_*` — die komen uit AdMob/Firebase. Relevant voor taak 63(h): **Advertising ID → ja** is dus verplicht, dat klopt met wat daar staat. - De **UMP-consent-SDK van AdMob zit al in de build** (logcat probeert `fundingchoicesmessages.google.com` te bereiken). Raakt P1-50 — de SDK is er, alleen de flow niet. - **Crashlytics initialiseert echt**: haalt zijn settings op met `status: activated` en het juiste `bundle_id`. Dat is nog geen bewezen crashrapport (P1-60), maar de koppeling leeft. - Cosmetisch: het logo boven in het **menu (Drawer)** staat deels ónder de statusbalk — de klok valt erachter. Zie `rel-menu.png`. **✅ Functionele doorloop GEDAAN 2026-09-19 (Claude), op een verse `--release`-build (`flags=0x0`) uit een export van diezelfde dag, mét `flutter_launcher_icons` vooraf, op de telefoon-AVD `emulator-5554` (1080x1920).** ⚠️ De poorten waren omgedraaid t.o.v. wat hierboven staat: 5554 was de telefoon, 5556 de tablet — check dat altijd met `wm size`. **Geen enkele crash.** 0 `FATAL` / 0 `E/flutter` over de hele doorloop: opstarten, Home, datumfilter, evenement-detail, drawer, horeca-overzicht, zaakpagina, Favorieten (uitgelogd), loginpagina, vliegtuigmodus en Engels. **Wat werkt (geverifieerd op het scherm):** Home met slider, kaarten en foto's · datumkiezer kiezen én annuleren (P2-45b, zie hieronder) · chips · alle vijf de tabs · drawer met alle iconen · horeca-overzicht met zoekveld, zes tabs en naast elkaar lopende categorielabels · zaakpagina met fotocarousel, vier tabs (Agenda eerst) en een nette lege-agenda-melding · AdMob-testadvertentie · Favorieten-tab *Gebruiker* met uitleg + "Account aanmaken" + "Inloggen" · loginpagina · **vliegtuigmodus: geen crash, de `VerbindingsBanner` verschijnt met "Geen verbinding met uitgaanskrant.com" en een knop *Opnieuw*, en de fallback-logo's vullen de lege lijsten** · **Engels: chips en tabs vertalen netjes** (All/Today/Weekend/This week, Go Out/Cityactivities/Culture & Info). **🔴 Nieuwe bevindingen uit deze doorloop — allemaal cosmetisch/UX, geen crash:** - **✅ 65a · Loginpagina links afgeknipt — OPGELOST 2026-09-19 (Claude).** De witte velden begonnen op x=0 (afgeronde hoek viel weg) en het hele formulier stond links terwijl de rechterhelft leeg bleef. *Oorzaak:* de `Stack` op deze pagina heeft maar **één** kind, dus de `Column` eronder krijgt losse breedtebeperkingen en wordt precies zo breed als zijn breedste kind — de twee `Container`s van 200 px. De pagina-`Column` was dus 200 van de 393 px breed en stond links. *Fix:* op die buitenste `Column` **Alignment X=0, Y=-1** (top-center) plus **Cross Axis Alignment = center**. Export: `Align(alignment: AlignmentDirectional(0.0, -1.0), child: Column(… crossAxisAlignment: CrossAxisAlignment.center))`. `dart analyze` 0 errors. ⚠️ **Let op — `crossAxisAlignment` alléén doet hier NIETS**, want de Column heeft geen overtollige breedte om in te centreren; de `Align` eromheen is wat het werk doet. Ditzelfde patroon (Stack met één kind → alles plakt links) kan elders in het project ook voorkomen. *Restpunt, ontwerpkeuze voor Bob:* de invoervelden zijn nog steeds vast **200 px** breed, dus op een telefoon ruim een halve schermbreedte. Nu netjes gecentreerd, maar breder (of `double.infinity` met padding) zou prettiger zijn. - **✅ 65b · Header schoof naar links op `HorecagelegenheidCurrent` — OPGELOST 2026-09-19 (Claude).** De hamburgerknop stond met zijn centrum op **x=53** (elders x=271/x=340) en stak dus links buiten beeld. *Oorzaak, gevonden door de twee pagina's naast elkaar te leggen:* deze pagina zet `HeaderButtonsComponent` in een **extra `Row`** binnen `FlexibleSpaceBar.background`, terwijl elke andere pagina het component daar rechtstreeks in zet. Het component is intern `Align > Row` en centreert zichzelf over de volle breedte; als kind van nóg een Row belandt het links op positie 0. *Fix zonder iets weg te gooien:* op die extra Row **Main Axis Alignment = center**. Export: `Row(mainAxisSize: max, mainAxisAlignment: MainAxisAlignment.center, …)`. **Op het toestel nagemeten:** knopcentrum van **x=53 naar x=270** — gelijk aan de andere pagina's (x=271). **Controleer dit patroon bij een nieuwe pagina:** `grep -n "background: Row(" lib/**/*_widget.dart` hoort alleen deze pagina te geven — overal elders staat er `background: wrapWithModel(`. - **65c · Favorieten toont uitgelogd een volledig LEEG scherm.** De standaardtab *Persoonlijke agenda* rendert niets: geen melding, geen uitleg, geen inlogknop. De uitleg + inlogknop staan op tab 4 (*Gebruiker*), maar die valt buiten beeld in de scrollende tabbalk — een uitgelogde bezoeker vindt 'm niet. **Onderzocht 2026-09-19 (Claude) — de voor de hand liggende route werkt niet:** de **Empty List Widget**-sectie van deze lijsten biedt als *Widget Type* alleen **Image** en **Component**, géén **Text**. Een geschreven uitleg kan er dus niet rechtstreeks in. (Toggle is weer uitgezet; het project staat zoals het stond.) **Twee routes die wél kunnen, allebei met een tekstkeuze die van Bob is:** (a) een klein component maken met de uitleg + de knoppen *Account aanmaken* / *Inloggen* (die staan al inline op tab 4, dus de inhoud bestaat) en dat als Empty List Widget op tab 1-3 zetten — meteen consistent op alle drie; of (b) per tab een `Text` ónder de lijst met een Visibility-conditie op `userSessionid` *Is Not Set or Is Empty* (die operator bestaat, zie `CLAUDE.md`). Route (a) is netter omdat hij óók werkt voor een ingelogde gebruiker zonder favorieten. - **🟡 65d · Deelknop op `EventCurrent` — KLEUR OPGELOST, padding nog open.** De knop had `fillColor: Color(0xFFF265A0)`, een hardcoded roze dat niet in het merkpalet zit. **Gezet op thema-token `Secondary`** (#FF680D, de merkregel is "oranje is wat je aanklikt"); `#F265A0` komt nu **0×** in het hele project voor. Geverifieerd met een verse export én op het toestel (knop is nu oranje). ⚠️ **Rest voor Bob, 10 seconden:** de knop raakt nog steeds de rechter schermrand. `EventCurrent` → widget tree filteren op `IconButton` → de `IconButton` onder `Row > Tooltip` → sectie **Padding** → **R = 12**. Claude komt niet bij dat veld: het ligt tegen de rechter paneelrand, drie pogingen landden niet en één klik opende per ongeluk de variabele-binder (zonder iets te binden — netjes geannuleerd). - **65e · "Datum" blijft Nederlands in de Engelse modus.** Het label van de kalenderknop komt uit de `valueOrDefault`-default `'Datum'` en is dus géén vertaalsleutel. Hoort bij P1-17. - Bevestigd, al bekend: het **logo in de drawer staat deels onder de statusbalk** (de klok valt erachter). **Nog steeds open van deze taak:** alles waarvoor je ingelogd moet zijn — hartje op een zaakpagina, Favorieten met inhoud, evenement aanmaken — plus Bob's Xiaomi. ⚠️ **Claude voert geen wachtwoorden in, dus het ingelogde deel is per definitie Bob's stuk.** De loginpagina zelf is geverifieerd tot aan het invoerveld. --- # ## 🚦 Bij livegang — schakelaars en opruimklussen · Eigenaar: Bob - **Builder-opruimronde P2-7, restant (Bob, 2026-09-17: "zet de rest bij livegang").** Nog in de export van 2026-09-17 00:10: componenten `SliderUitgaanComponentSmallCurrent` en `PUitgaantabelKaartComponentOrgineelMetKaartjeerin`; pagina's `KanwegTestUpload` en `Event` (die laatste weigert structureel, laten staan); custom function `dISABLEdatumVoorApi` (hernoemd, nog steeds dood). Weg zijn sinds 2026-09-16/17: `drawerComponentCopy`, `kanweghorecagelegenheidCurrentCopy`, `KanwegHorecagelegenhedenOverzicht`, `KanwegHomeCopy`, `HeaderButtonsComponentCopymethartje`, de drie `KaartTabel/Slider`-comps, `KanwaegSelectStateDropDownComponentCopy`, `KanwegHomeUitgaantabelKaartComponentCopy`, `KanwegHeaderButtonsComponentCopy`, `FavorietenCopy`, `HorecagelegenhedenOverzichtCopy/Copy2/Copy2Copy`, `Kanweg`, `KanwegHorecagelegenhedenOverzichtSortPage`. `dart analyze` op die export: 0 errors. - **Taak 56 · Opruim-cron: description zegt 30 dagen, code doet 360 (Bob's besluit 2026-09-15: na livegang).** In `custom_cronapi()` staat *"after 30 days"*, in `custom_delete_expired_events_callback()` staat `$date->modify('- 360 days')`. Met 16.065 events in de voorraad is dat geen detail. Er zit bovendien een `range(0, 500)` op: max 500 verwijderingen per cronrun, wat bij de huidige importsnelheid mogelijk niet meer volstaat. Besluit welke van de twee klopt, maak description en code gelijk, en hernoem `$date_after_30`. - **AdMob: `Show Test Ads` uitzetten** (App Settings → AdMob). Staat nu AAN, dus de banner op `pUitgaanPage` toont testadvertenties en levert niets op. De GDPR-consentflow staat sinds 2026-09-15 wél goed (`adMobRequestConsent()` in `main.dart`), dus het is alleen deze schakelaar nog. - **P1-17 · alle Engelse vertalingen in één ronde** (Bob's besluit 2026-09-10: niet nu, want er komen onderweg nog teksten bij). Nu open: 46 velden in `stadsactiviteitAanmaken` (27) en `uitgaansevenementAanmaken` (19), plus de twee nieuwe teksten uit taak 22 op `SelectStateDropDownComponent`: **"Gemeente volgen"** (`nosgkxy0`) en de uitlegzin **"Bij favoriete gemeenten krijg je een agenda per mail met daarin alle evenementen uit die favoriete gemeenten."** (`24bdnkhi`), en sinds 2026-09-15 ook **"Je volgt deze gemeente"** (`elx7kse2`) — let op: die kreeg door `Duplicate` de *verkeerde* EN-tekst "Add to favorites" mee, dus niet leeg maar fout; `nosgkxy0` staat in EN ook nog op "Add to favorites" en hoort "Follow municipality" te worden. - **Titels met spaties vooraan/achteraan opschonen (was P1-47, verbreed 2026-09-11).** Bob mat op devbob **100+** `horecagelegenheid`-nodes met een gewone ASCII-spatie vóór de titel (`LIMIT 100` liep vol, dus het werkelijke aantal is hoger); ook tabs (nid 49539) en spaties áchteraan (49512) komen voor. MySQL sorteert op de rauwe `node.title`, dus elke zo'n node valt buiten de alfabetische volgorde — terwijl de JSON schoon oogt, want `_custom_clean_html` poetst ná de query. **Bob's besluit 2026-09-11: de impact is nu te groot, we lopen de namen door bij de livegang.** Te verifiëren op productie met de telquery hieronder; de drie losse titels uit taak 16 zijn daar al gefixt, de rest vermoedelijk niet. ``` drush @prod sqlq "SELECT type, COUNT(*) FROM node WHERE title <> TRIM(BOTH ' ' FROM TRIM(BOTH CHAR(9) FROM TRIM(BOTH CHAR(10) FROM TRIM(BOTH CHAR(13) FROM TRIM(BOTH ' ' FROM title))))) GROUP BY type;" ``` De fix is een `UPDATE` met diezelfde genestelde `TRIM` op **`node` én `node_revision`**, idempotent, met vooraf een `sql-dump` van die twee tabellen. URL-aliassen blijven ongemoeid. - **P2-7 · opruimronde, 23 dode eenheden** (Bob 2026-09-13: naar livegang). ⚠️ **Twee nieuwe sinds 17 sep, allebei van Claude:** de pagina **`favorietenCopy2`** (oefenkopie van `Favorieten`, gemaakt om de Wrap-Widget-route voor taak 77 veilig uit te proberen — zie de les in `CLAUDE.md`) en het component **`HorecagelegenheidEventTabelComponentCopy`**, dat bij een hernoeming `...Current` werd (controleer of de oude map nog in `lib/` of git staat). Claude gooit niets weg, dus beide staan er tot jij ze opruimt. Moet in de **builder** gebeuren, niet met `git rm` — anders staat alles na de volgende export terug. *9 componenten:* `header_buttons_component_copymethartje`, `kaart_slider_uitgaan_s_comp`, `kaart_tabel_uitgaan_comp`, `kaart_tabel_uitgaan_s_comp`, `slider_uitgaan_component_small_current`, `kanwaeg_select_state_drop_down_component_copy`, `kanweg_home_uitgaantabel_kaart_component_copy`, `drawer_component_copy`, `p_uitgaantabel_kaart_component_orgineel_met_kaartjeerin`. *12 pagina's:* `Event`, `FavorietenCopy`, `HorecagelegenhedenOverzichtCopy`, `HorecagelegenhedenOverzichtCopy2`, `HorecagelegenhedenOverzichtCopy2Copy`, `Kanweg`, `KanwegHomeCopy`, `KanwegHorecagelegenhedenOverzichtSortPage`, `KanwegTestUpload`, `KanwegHorecagelegenhedenOverzicht`, `KanweghorecagelegenhedenOverzichtCurrentCopy`, `KanweghorecagelegenheidCurrentCopy`. **Begin met `drawer_component_copy` en `kanweghorecagelegenheid_current_copy`** — die twee houden `KanwegHorecagelegenhedenOverzicht` als enige nog in leven. ⚠️ `HorecagelegenhedenOverzichtCopy2` pas weggooien als P2-6 af is (het is de intacte momentopname waaruit het herstel van 09-01 is afgeleid), en de pagina `Event` weigerde in augustus élke wijziging met "Invalid Action" — lukt die na één poging niet, laten staan. - **P1-30 · Cloudflare rate limiting** aanzetten, en dan óók de twee rate-limiting-vinkjes weghalen uit de custom rule `flutterflow`. - **P2-25 · Cleanup `custom.module` — debugging alleen op devbob · Eigenaar: Bob (Drupal-code, aangemaakt 2026-09-14).** Vóór livegang moet de custom module (`custom.module` + `custom.evenementen_aanmaken.inc` en de overige `.inc`-bestanden die de `flutterdrup`-Services-resources voeden) goed werken **zonder overbodig te loggen**. Nu zit er debug-uitvoer in die tijdens het bouwen van P2-15/de favorieten handig was, maar op productie alleen de watchdog/dblog volschrijft. Bewust **pas aan het einde** doen, na de laatste functionele Drupal-wijzigingen — anders raak je de debug-info kwijt terwijl je 'm nog nodig hebt. Aanpak: 1. Inventariseer alle `watchdog(...)`/`dpm(...)`/`error_log(...)`/ `drupal_set_message(...)`-debugregels in de custom module (`grep -n "watchdog\|dpm(\|error_log\|drupal_set_message" custom.module *.inc`). 2. Splits in **echte fouten** (blijven, `WATCHDOG_ERROR`/`WATCHDOG_WARNING`) en **debug/trace** (request-body dumps, "resource aangeroepen"-regels, tussenwaarden). 3. Debug/trace achter één schakelaar zetten die alleen op devbob aan staat, bv. een Drupal-variabele `custom_debug` (`variable_get('custom_debug', FALSE)`) in een kleine helper `_custom_debug($msg, $vars)`, zodat het op productie standaard uit staat en op devbob met één `drush vset custom_debug 1` weer aan kan. Geen debug-code weghalen die bij een storing op productie nog nuttig kan zijn — alleen uitzetten. 4. ✅ **Nagelopen 2026-09-17 (Bob, grep op productie):** alleen een uitgecommentarieerde `print_r` (r.791) en een `print_r` binnen `_custom_debug_watchdog` (r.1948, gaat via de debug-schakelaar). Niets dat de JSON kan vervuilen. Oud: nalopen dat er geen `print`/`var_dump`/`dd()` meer in staat die de JSON-respons kan vervuilen (de app parst de body strikt; één regel ervoor breekt `getJsonField`). 5. Deploy naar productie, Drupal-cache legen, en met een cache-buster nameten dat elk endpoint nog dezelfde JSON geeft (zie het `_cb=$RANDOM`-recept in `CLAUDE.md`) én dat de dblog na een testronde in de app schoon blijft. Hoort samen met P1-30 (Cloudflare) in dezelfde livegang-ronde. - **Max items + tab 6** (P2-24 A en C) — geparkeerd, zie daar. Melden bij FlutterFlow-support is de enige overgebleven route. **Afgerond 2026-09-11:** taak 18 (route van drawer-item *Horeca* en van de knop *Alle horecagelegenheden* omgezet naar `horecagelegenhedenOverzichtCurrent`, beide met de `plaats`-binding op `gemeenteSelectId` opnieuw gezet — die wist FlutterFlow bij een paginawissel; oude pagina hernoemd en naar `kanweg` verplaatst) en taak 22 (label + uitlegzin bij het gemeentehartje, allebei achter de login-guard). **Afgerond 2026-09-10:** taak 16 (drie titels met onzichtbaar teken — geverifieerd: "Café Arnhem" staat nu alfabetisch juist), taak 17 (contentfoutjes), P0-13 (login-guards op de hartjes), P1-48 (lege-staat op de Home-tabs), en de horeca-sortering (alfabetisch, live). --- --- ## 🗄 Drupal / views — open # ### ✅ P1-42 + P1-43 · Verlopen evenementen in de lijsten — AF, nagemeten op productie 2026-09-19 (Claude) **Alle drie de onderdelen zijn opgelost; er is niets meer te fixen.** Gemeten met cache-buster op 2026-09-19 02:20: - `flutterflowmobiel1` services_1/3/4/5/6/7: elk 50 items op pagina 0, **allemaal vanaf vandaag en oplopend gesorteerd**, nul verlopen items. (`services_2` geeft 0 items — stond al als ongebruikt bekend.) - `flutterflowmobiel_establishment_events`: Bimhuis (75459) 14 items, Carré (67233) 30 — **alleen toekomstig, chronologisch**. De 43%-verlopen-meting uit de audit van 6 sep geldt niet meer. - `flutterflowmobiel_establishments`: **alfabetisch** op titel (Afas, Arti et Amicitiae, Betty Asfalt, Beurs van Berlage, Bimhuis…), niet meer nid-aflopend. **Het blijvende inzicht — de valkuil bij een volgende viewwijziging:** op `flutterflowmobiel1` **erven de displays de sortering wél** (geen enkele zet `defaults['sorts']` uit, dus één wijziging op de Master raakt alle zeven), maar **zes van de zeven zetten `defaults['filters'] = FALSE` en erven dus géén filter**. Een criterium op de Master bereikt daardoor maar één display — vandaar dat de sortering destijds wél aansloeg en het datumfilter niet. Wil je een filter over alle displays tegelijk: `hook_views_query_alter()` (zoals `datum_van`/ `datum_tot`/`zoek` al doen), niet de Views-UI. En géén *exposed* date_views-filter gebruiken: dat overleeft PHP 8 niet (500 op een platte string, 0 rijen op de correcte vorm). Staat ook in `CLAUDE.md`. Verificatiecommando: ``` for d in 1 3 4 5 6 7; do printf "services_%s: " $d; curl -s "https://uitgaanskrant.com/nl/flutterdrup/views/flutterflowmobiel1.json?display_id=services_$d&page=0&_cb=$RANDOM" | python3 -c "import sys,json;d=json.load(sys.stdin);print(d[0]['datum'],'->',d[-1]['datum'])"; done ```
Oorspronkelijke taakomschrijving + audit van 2026-09-05/06 (historie) ### P1-42 · Uitgaanslijsten tonen overwegend verlopen evenementen · Eigenaar: Bob (Drupal/views) **P1-42.** **De uitgaanslijsten tonen overwegend verlopen evenementen.** Gevonden 2026-09-05 (Claude, live `curl` tijdens P2-1-onderzoek), geldt voor `flutterflowmobiel1` services_1/2/3 — de views waar Home zijn tabs mee vult. - Gemeten op 2026-09-05 over de eerste 6 pagina's (150 items): **juli 66, augustus 48, september 36** — dus ±76% van wat de lijst toont was op de meetdatum al geweest. - De lijst gaat vrijwel eindeloos terug: `page=40` levert nog steeds 25 items (eind mei), `page=80` idem (half mei). Met infinite scroll aan scrollt een gebruiker dus de geschiedenis in. - **Oorzaak:** de view sorteert op `created DESC` (zichtbaar in Bob's view-export van `flutterflowmobiel_establishment_info`, en het gedrag van `flutterflowmobiel1` past daarbij) — dus op **wanneer iemand het evenement invoerde**, niet op wanneer het plaatsvindt. Dat verklaart ook waarom de volgorde binnen een pagina rommelig is (95 van de 149 opeenvolgende paren staan op datum, de rest niet). - **Fix, één ingreep in de view:** filter `datum >= vandaag` én sorteer **oplopend** op de datum-veldwaarde i.p.v. `created DESC`. Dan staat het eerstvolgende evenement bovenaan en loopt scrollen de toekomst in. - **Stand 2026-09-15:** het datumfilter (Vandaag / Dit weekend / Deze week) is inmiddels gebouwd en werkt, en de master van `flutterflowmobiel1` filtert `>= -2 hours` en sorteert oplopend. Dit punt gaat dus nog alleen over de vraag of dat overal doorwerkt. - ⚠️ **Nog te verifiëren door Bob:** of dit ook echt zo in de app oogt (gemeten op de API, niet op een toestel), en of de horeca-/ favorieten-lijsten dezelfde sortering hebben. **Audit afgerond 2026-09-06 (P1-43, Claude — live `curl`, 6 pagina's = 150 items per display, peildatum 6 sep 2026). Het probleem is NIET beperkt tot services_1/2/3 — het raakt elke evenementenlijst in de app:** | view / display | waar in de app | verlopen | volgorde | |---|---|---|---| | `flutterflowmobiel1` services_1 | Home-slider | 135/150 (90%) | 97/149 paren oplopend | | `flutterflowmobiel1` services_2 | Home-tabs (alle vijf, zie P1-44) | 142/150 (94%) | 87/149 | | `flutterflowmobiel1` services_3 | Uitgaan-tab + P-pagina's | 143/150 (95%) | 100/149 | | `flutterflowmobiel1` services_4 | Activiteiten-tab | **150/150 (100%)** | 75/149 | | `flutterflowmobiel1` services_5 | Cultuur & Info-tab | 142/150 (94%) | 87/149 | | `flutterflowmobiel1` services_6 | Films-tab | **150/150 (100%)** | 84/149 | | `flutterflowmobiel1` services_7 | Jeugd-tab | 149/150 (99%) | 89/149 | | `flutterflow_events` services_1 | evenement-detail (op nid) | 117/150 (78%) | 102/149 | | `flutterflowmobiel_establishment_events` | horeca-detail, agenda van de zaak | 42/97 (43%) | niet chronologisch | | `flutterflowmobiel_establishments` | horeca-overzicht | n.v.t. (geen datum) | **nid aflopend = nieuwste zaak eerst** | | `flutterfavorietenagenda` | Favorieten tab 1 | niet gemeten | niet gemeten | Wat daar per regel bij hoort: - **services_4, _6 en _7 zijn feitelijk archief**: over 150 items geen enkel (services_4, _6) of één (services_7) toekomstig evenement. services_4 loopt terug tot november 2025, services_7 zit voor 110 van de 150 items in mei 2026. - **Met een `townid` erbij wordt het erger, niet beter.** services_3 met `townid=25434` (Arnhem) geeft over de **volledige** paginering 177 evenementen, waarvan **0 toekomstig**; oudste 25 oktober 2025. Dat is deels een inhoudsgat (Willemeen en Theater a/d Rijn hebben in `establishment_events` óók geen toekomstige data), maar door `created DESC` krijgt de bezoeker wel een pagina die volledig uit verleden bestaat, zonder enige aanwijzing dat dat zo is. - **De horeca-agenda op een zaakpagina heeft hetzelfde probleem in het klein**: maximaal 10 items, geen datumfilter, niet op datum gesorteerd. In een steekproef van 15 zaken hadden 4 zaken uitsluitend verlopen evenementen in hun agenda staan (75620, 75633, 67258, 70176). - **Het horeca-overzicht sorteert op nid aflopend** — geverifieerd over 6 categorieën in Arnhem, telkens exact aflopend en nooit alfabetisch. Dat is dus "nieuwste inschrijving eerst", wat voor een naslaglijst weinig betekent. Voorstel: alfabetisch op naam (voorspelbaar, en het zoekveld uit P2-6 sluit daarop aan). - **De favorieten-agenda is niet zonder sessie te meten**: anoniem geeft `favorieten_agenda.json` een **403** met body `["Toegang geweigerd voor gebruiker anonymous"]`. `drupalRequest` maakt daar (sinds de wijziging van 2026-08-17) een lege `[]` van, dus de app crasht niet — maar een verlopen sessie ziet er in de app uit als "je hebt geen favorieten", zonder melding. Klein los punt, niet dringend. - Meetscript staat in de scratchpad van deze sessie (`audit/measure.py` + `run1..8.py`); het is 20 regels en zo weer opgetuigd — jaartal komt uit het `/20xx/`-segment van het `url`-veld, want `datum` bevat geen jaar. **Wat dit betekent voor de ingreep in Drupal:** het is één patroon over alle displays van `flutterflowmobiel1` heen, plus `flutterflowmobiel_establishment_events`. Zelfde fix (filter `datum >= vandaag`, sorteren oplopend op de datumveldwaarde) op alle zeven displays + de zaak-agenda in één ronde, en apart de vraag of het horeca-overzicht niet gewoon alfabetisch moet.
### P2-24 · Restpunten horeca-zoekfilter · Eigenaar: Bob **✅ Stand 2026-09-19 (verse export, Claude):** **A is af** (alle zes de `StaggeredView`s, ook tab 6 Verhuur/catering, draaien op `filterHorecagelegenheden(...)` met `alleVerhuurCatering`), **C is af** (`.take(25)` komt niet meer voor, 6× `.take(1000)` = dezelfde grens als `maxPaginas`), **D is af** (taak 18) en **E is af** (0 lege `TextField`-hints). **Nog open: alleen B. Besluit Bob 2026-09-19: TAB-BEWUST.** Eigenaar daarmee Claude. Bob's nuance: de tab is de *hoofdcategorie*, de opties zijn de *subcategorieën* daaronder. Een zaak kan op twee tabs staan, maar per tab telt alleen de subcategorie die bij díe hoofdcategorie hoort. Afleiden uit de data van de tab geeft dus een paar vreemde opties (bv. "Catering" op Eetgelegenheden, 6× in Amsterdam); netter is de optielijst uit de taxonomie-kinderen van de tab-tid (`horcat` 17969/17963/34/17965/17967/17968). Eerst checken of een bestaand endpoint (`categorieen`?) die kinderen per tid kan leveren. F (duplicaten) is op Bob's verzoek geschrapt. De blokkade-verhalen hieronder zijn daarmee historie; ze staan er nog omdat de meetmethode nuttig blijft. **🔧 B · Stand 2026-09-19 (bijgesteld): het endpoint bestond al — alleen de access-regel moet om.** Mijn eerdere conclusie ("kan niet in de app, nieuw Drupal-endpoint nodig") was te somber; ik keek naar het verkeerde endpoint. **Er zijn TWEE categorie-endpoints, en dat is makkelijk te verwarren:** | Resource | Vocabulary | Waarvoor | |---|---|---| | `categorieen` | vid **10** (`field_act_categorie` / `field_category2`) | Stadsactiviteit + Uitgaansevenement | | `horecacategorieen` | vid **6** (`field_categories`, `horecagelegenheid_category`) | **Horecagelegenheid — dit is degene die we nodig hebben** | Dat de horeca-tabs in vid 6 zitten blijkt uit `custom_form_alter()` in `custom.module`: `taxonomy_get_tree(6, 34)` plus `array(1 => 17969, 2 => 17963, 3 => 34, 4 => 17967, 5 => 17968)` — exact de zes `horcat`-waarden. **`_custom_horecacategorieen_service()` geeft al de juiste vorm terug** — het loopt via `_custom_term_reference_field_options()`, en die levert per term `tid`, `naam`, `diepte` én **`parent_tid`**. Er hoeft dus niets aan die functie te gebeuren. **Enige blokkade: `access callback => user_is_logged_in`.** Het horeca-overzicht is een publieke pagina. → Bob, zie zijn taak hierboven. **Waarom het niet zonder dit endpoint kan:** het veld `categorie` van `flutterflowmobiel_establishments` bevat *alle* labels van een zaak, over al zijn hoofdcategorieën heen. Tab *Eetgelegenheden* levert zo 57 verschillende labels op met o.a. "Catering" (26×) en "Zaalverhuur"; tab *Cultuur* geeft "Cafe" (11×). Een zaak die op twee tabs staat is uit die respons principieel niet toe te wijzen aan de juiste hoofdcategorie. Met `parent_tid` uit `horecacategorieen` is dat één filterregel. **✅ Gemeten op devbob 2026-09-19 (Bob heeft de access callback omgezet):** HTTP 200 anoniem, 129 records, 8,8 KB, 0,22 s. Exact **6 termen op diepte 0** — de zes tab-tids — en 123 op diepte 1. Dieper dan 1 gaat de boom niet, dus "directe kinderen van de tab-tid" is precies de goede filterregel. | Tab | `horcat` | naam in de boom | subcategorieën | |---|---|---|---| | Activiteiten | 17969 | Activiteit | 26 | | Cultuur | 17963 | Cultuur & Info | 15 | | Eetgelegenheden | 34 | Eetgelegenheden | 52 | | Overnachten | 17965 | Overnachten | 4 | | Uitgaan | 17967 | Uitgaansgelegenheid | 19 | | Verhuur/catering | 17968 | Verhuur, Verkoop & Catering | 7 | ⚠️ De naam van de wortelterm wijkt soms af van het tablabel (*Uitgaan* vs *Uitgaansgelegenheid*). Onschadelijk — er wordt op **tid** gefilterd, niet op naam. `horcatSubTids`/`horcatSubLabels` zijn tegen deze echte respons nagerekend: per tab even lang, geen lege labels, geen duplicaten, en alle randgevallen (`respons` null of leeg, `horcatTid` null/leeg, onbekend tid) geven netjes een lege lijst. **App-deel (Claude), zodra het endpoint publiek is:** 1. ✅ **AF** — custom functions `horcatSubTids` en `horcatSubLabels` staan in het project (2026-09-19, export-geverifieerd, `dart analyze` 0 errors). 2. Dropdown per tab op `horecagelegenhedenOverzichtCurrent`, gevoed uit één `horecacategorieen`-call (`cache: true`). 3. Extra argument op `filterHorecagelegenheden(...)` — zelfde route als het zoekveld al gebruikt. 4. Ontwerpkeuze voor Bob: eerste optie "Alle categorieën" of standaard leeg? ⚠️ **Verifieer als eerste** dat de zes tab-tids (34/17963/17965/17967/17968/17969) op `diepte: 0` in de respons staan — dat bevestigt dat `field_categories` op vid 6 zit. Zo niet, dan wijst `field_categories` naar een andere vocabulary en moet de service een ander veld introspecteren. **P2-24.** Pagina: **`horecagelegenhedenOverzichtCurrent`**. P2-6 heeft het zoekveld op 5 van de 6 tabs werkend en live op een toestel bevestigd (zie het ✅-blok bij P2-6). Dit zijn de punten die nog over zijn; ze hebben allemaal Bob nodig — óf omdat Claude er aantoonbaar niet doorheen komt, óf omdat er een keuze in zit. **Het werkende recept per `StaggeredView` en alle valkuilen staan bij P2-6; niet opnieuw uitzoeken.** **⛔ A en C zijn geblokkeerd door één en dezelfde oorzaak — lees dit eerst (2026-09-09, Claude, hard gemeten).** Het **Generate Dynamic Children-paneel schrijft op deze pagina niets meer weg.** Dat is een nieuwe, veel bruikbaardere diagnose dan het oude verhaal ("de bron Custom Functions rendert zijn optielijst niet") — dat was een symptoom, niet de oorzaak. Het bewijs: - De **Max Items**-waarde (25) is op **zes** StaggeredViews aangepast. In de UI toonde het veld daarna netjes leeg (*"Leave empty for no limit…"*), en bij een tweede test de waarde `1000`. **Een verse export toonde in beide gevallen onveranderd `.take(25)`, zes keer.** Leeg én een getal komen dus allebei niet door — het ligt niet aan een lege waarde. - Committen via een klik op lege paneelruimte, via een klik in het `Variable Name`-veld, én via een selectiewissel in de widget tree: alle drie geen verschil. - **De pagina zelf is níét bevroren:** Bob's verwijdering van de twee lege `TextField`s (punt E) kwam op dezelfde dag wél gewoon door, net als de hernummering van `textController1` → `textController` die FlutterFlow daarbij zelf doorvoerde. **Gevolg:** zowel punt A (tab 6 koppelen) als punt C (`Max Items` weghalen) lopen via dit paneel en zijn daarmee niet uitvoerbaar. Bob heeft A twee keer geprobeerd, Claude ~15 keer plus zes keer op Max Items; het bestand kwam elke keer **byte-identiek** terug uit de export. **Duplicaat-test GEDAAN 2026-09-10 (Bob's akkoord) — en die werkt NIET.** `Duplicate Page` op Copy3 gaf `horecagelegenhedenOverzichtCurrentCopy`. Daar meteen dezelfde ingreep geprobeerd: Max Items van `25` naar leeg op de eerste `StaggeredView`; de UI toonde weer netjes *"Leave empty for no limit…"*. **Verse export: 6× `.take(25)`, precies als in het origineel.** De blokkade zit dus **niet in de opgeslagen pagina-data** — een verse kopie erft 'm gewoon. Daarmee vervalt de beste hypothese en is er geen route meer die Claude of Bob in de builder kan proberen. **⚠️ Opruimen: `horecagelegenhedenOverzichtCurrentCopy` moet weg** (Bob — Claude verwijdert niets). De pagina heeft verder niets gedaan en wordt nergens naartoe genavigeerd; hij bestaat alleen als restant van deze test. Toevoegen aan P2-7. **Wat dan wel:** Bob's terugvaloptie van 2026-09-10 — *"laten we dat probleem, max items, even voor wat het is; moeten we dat een punt maken voor de livegang."* Dus **A en C blijven open als livegang-punt.** De enige overgebleven route is een melding bij FlutterFlow-support, want dit is aantoonbaar een bug aan hun kant: hetzelfde paneel accepteerde deze wijzigingen in september nog wél, en een ander paneel op precies dezelfde widget (Empty List Widget, zie P1-46) schrijft gewoon weg. **A. Tab 6 (Verhuur, catering) alsnog koppelen.** De enige `StaggeredView` die nog op de rauwe API-respons staat; alle andere vijf draaien op `filterHorecagelegenheden(...)`. Recept staat bij P2-6, met `items` = **`alleVerhuurCatering`**. ⚠️ **Claude komt hier niet doorheen** — twee sessies, ~15 pogingen: in de Set-Variable-dialoog van uitgerekend deze ene `StaggeredView` rendert de bron **Custom Functions** zijn optielijst nooit. Uitklappen lukt (chevron slaat om), maar de rij `filterHorecagelegenheden` eronder verschijnt niet — niet na hoveren, niet na blind klikken op de verwachte positie, niet na de dialoog te sluiten en te heropenen, en niet na een volledige herlaad van de builder. Op de andere vijf tabs werkte exact dezelfde reeks wél. Niets kapot: de tab staat gewoon nog op zijn oorspronkelijke binding en werkt zoals voorheen. **B. Categoriedropdown (was P2-6 stap 4) — eerst een ontwerpkeuze van Bob, daarna kan Claude bouwen.** Er is een custom function nodig die de unieke `categorie`-waarden uit de dataset haalt. **Claude heeft die bewust nog NIET aangemaakt**: er zit een productkeuze in, en een eenmaal aangemaakte custom function kan Claude niet meer verwijderen (staande regel). Twee varianten: - **Tab-bewust** (volgt de oorspronkelijke spec): zes `alleXxx`-lijsten + `TabBar Current Index` (Widget State) + het pad = **8 argumenten**. De dropdown toont precies de categorieën van de zichtbare tab. Nadeel: 8 bindingen in juist die dialoog die bij punt A vastliep. - **Samengevoegd** (simpeler): zes lijsten + het pad = **7 argumenten**, toont alle categorieën over alle tabs heen. Kiest de gebruiker er een die op de actieve tab niet voorkomt, dan is de lijst leeg. Fors minder bindwerk. Zeg welke, dan bouwt Claude de functie én de dropdown (invoegen in de root-`Column` is bewezen werkend, net als bij het zoekveld). ⚠️ **`categorie` is in de API een LIJST, geen string** — bevestigd 2026-09-05 met curl op Arnhem (`townid=25434`): `"categorie": ["Bioscoop"]`. De bestaande `filterHorecagelegenheden` gaat daar al goed mee om, maar de nieuwe unieke-categorieën-functie moet de binnenlijst **plat slaan** en niet `.toString()` op het hele veld doen — anders krijg je opties als `[Bioscoop]` die nooit matchen. Aantallen in Arnhem: Activiteiten 2 items / 2 categorieën, Eetgelegenheden 20 / 19, Uitgaan 4 / 4. **C. `Max Items` staat per tab op 25 — Bob's besluit 2026-09-09: weghalen. ⛔ Geblokkeerd, zie het kader hierboven. Dit is een ECHTE blocker, geen cosmetiek.** Gemeten op **productie** in Amsterdam (`townid=28695`): | tab | items | zichtbaar met `take(25)` | |---|---|---| | Eetgelegenheden | **245** | 25 | | Uitgaan | 75 | 25 | | Activiteiten | 42 | 25 | | Overnachten | 42 | 25 | | Verhuur, catering | 24 | 24 | | Cultuur | 14 | 14 | **⚠️ CORRECTIE 2026-09-10 op een eerdere bewering hier: het zoekveld doorzoekt WÉL de volledige lijst.** De gegenereerde code is `filterHorecagelegenheden(, zoekterm, …).toList().take(25).toList()` — dus eerst filteren over alle 245, dán afkappen. Zoek je "pizza" in Amsterdam, dan worden alle 245 doorzocht en zie je de eerste 25 treffers. De eerdere formulering "het zoekveld doorzoekt alleen die 25" was fout. **Wat er wél overblijft:** zonder zoekterm zie je 25 van de 245 en kun je **niet doorbladeren**, want deze pagina heeft geen pager — geverifieerd: 0 `PagedMasonryGridView`, 0 `PagingController`, 0 infinite scroll, zes gewone `MasonryGridView`. De infinite scroll is er bij P2-6 bewust uitgehaald omdat client-side filteren de volledige dataset nodig heeft. In Amsterdam/Eetgelegenheden zijn 220 zaken dus alleen via het zoekveld bereikbaar, niet door te scrollen. *(Een eerdere inschatting "je merkt er weinig van" was gebaseerd op Arnhem, grootste tab 20 items. Te klein om iets over limieten te zeggen; gebruik voortaan Amsterdam.)* **Haalbare verzachting zolang `take(25)` vastzit — voorstel voor Bob:** sorteer de lijst **in `fetchAlleHorecagelegenheden`** (een custom action, en de Custom Code-editor werkt gewoon). Nu is de volgorde die van de view (nid aflopend), dus je krijgt 25 min of meer willekeurige zaken. Alfabetisch gesorteerd krijg je 25 voorspelbare, en samen met het zoekveld is dat werkbaar. Dit dekt meteen de openstaande wens "Horeca-overzicht sorteren" (zie de wachtrij bovenaan) zónder dat de Drupal-view aangepast hoeft te worden. **Weghalen is veilig zodra het kan:** er is al een harde begrenzing elders (`fetchAlleHorecagelegenheden` loopt door API-pagina's van 100 met `maxPaginas = 10`, dus **max 1000 per tab**), en alle zes de grids zijn `MasonryGridView.builder` — lazy, dus alleen zichtbare kaarten worden gebouwd. Er is **geen paginering/infinite scroll meer** op deze pagina (0 `PagingController`s); dat is bewust, want client-side filteren kan alleen met de volledige dataset. Wil je ooit strakker begrenzen, doe dat in `maxPaginas`, niet in de weergavelimiet. **D. Route omzetten (was P2-6 stap 7) — bewust uitgesteld tot A en B klaar zijn.** Twee `Navigate To`-wijzigingen naar `horecagelegenhedenOverzichtCurrent`: `lib/shared/drawer_component/drawer_component_widget.dart` en `lib/horecagelegenhedenoverzicht/horecagelegenheid_current/horecagelegenheid_current_widget.dart`. Daarna wordt de oude `HorecagelegenhedenOverzicht` een orphan → toevoegen aan P2-7. Ook `Copy3` heet dan nog "Copy3" terwijl het de levende pagina is; hernoemen of niet is Bob's keuze. **E. Twee lege `TextField`-placeholders opruimen.** In tab 0 (Activiteiten) en tab 1 (Cultuur) staat nog het oude, ongebonden `TextField` met hint letterlijk "TextField". Op een toestel is dat een zwevend wit vak dat half over de `TabBar` valt — lelijk en verwarrend naast het echte zoekveld. De eerdere afspraak "laten staan tot P2-6 ze van een echte binding voorziet" is hiermee afgehandeld: het echte zoekveld staat nu bóven de `TabBar`, dus deze twee zijn overbodig. Claude verwijdert niets. **Niet oplosbaar, geaccepteerd:** FlutterFlow zet op de `On Change`-trigger zelf een **`EasyDebounce` van 2000 ms** en die is nergens instelbaar (geen `debounce`-eigenschap op het widget, en de trigger-/actiemenu's bieden 'm niet). Live merkbaar: de lijst ververst ~2 s nadat je stopt met typen. Werkt correct, voelt traag. ### ✅ Taak 28 · events zonder categorie — AF (Bob, 2026-09-19) Bob meldt de Drupal-kant gefixt (events zonder categorie + "Tweedehands markt"). Nog niet door Claude nagemeten; het meetrecept (alle zeven displays pagineren, nids naast `flutterflow_events` leggen, classificeren op datum+tijd) staat in `CLAUDE.md`. Bij de volgende contentmeting even meenemen. ## 📚 Referentie — geen taak, wél lezen vóór je iets opnieuw onderzoekt # ### 📌 Nagemeten, niet opnieuw onderzoeken - **Het P1-15-patroon is projectbreed schoon.** Alle 16 `launchURL`-aanroepen in levende code hangen achter een guard op de **rauwe** waarde, niet op een `valueOrDefault`. - **`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. - **P1-45 is echt weg**: `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. - **Ongelezen component-parameters**: alleen `EvenementInfo` (6 van de 12, nagemeten en juist), de wees hierboven, en `HorecagelegenheidoverzichtKaart.nid` (restant van het verwijderde hartje). ### 🚫 Bob's besluiten 2026-09-13 — niet meer voorstellen - **Titels met onzichtbare tekens opschonen (was taak 42 / P1-47): AFGEWEZEN.** Bob: "we wachten tot horecagelegenheden het zelf aanpassen, of we passen het als editors een keer aan. Het is heel veel oude data, daar ga ik geen tijd in steken. Als ik er tegenaan loop, pas ik het aan." **Niet opnieuw agenderen.** Ter informatie, zodat het effect bekend is: 18 van de eerste 100 zaken in het horeca-overzicht staan hierdoor buiten de alfabetische volgorde, en ze staan allemaal *bovenaan* (MySQL sorteert op de rauwe `node.title`, een spatie komt vóór de A). De eerste kaarten die een gebruiker ziet zijn dus willekeurig. - **minSdkVersion-beslispunt (was taak 43): VERVALLEN, er valt niets in te stellen.** Bob's vraag ("dat gaat toch automatisch via FlutterFlow?") is terecht. Gecontroleerd 2026-09-13: `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. - **P2-7 opruimronde (was taak 44): naar de livegang-lijst.** - **Engelse vertalingen (was taak 45): naar de livegang-lijst**, deels al gedaan. Bob 2026-09-13: "dit zullen we nog wel een paar keer krijgen, nieuwe pagina's met vertalingen die missen, en dat wil ik later in 1x." - **Events zonder categorie (taak 32 deel B): Bob heeft er een eigen taak van gemaakt. Wél een probleem, géén livegang-blokker** — zijn expliciete oordeel. ### 📊 Contentstand productie — hermeten 2026-09-14 21:50 (Claude, alle displays volledig gepagineerd) | | 09-13 | **09-14** | |---|---|---| | unieke events (`flutterflow_events`) | 9625 | **16.065** (18.207 rijen) | | **toekomstige** events | 38 | **6.202** | | laatste datum in voorraad | 25 sep | **10 jan 2027** | | per maand | — | sep 1487 · okt 2297 · nov 1412 · dec 894 · jan 112 | **Het contentgat is dicht** — de import levert nu maanden vooruit. De zeven `flutterflowmobiel1`-displays zijn schoon: 0–5 verlopen items per display op duizenden rijen (Uitgaan 3275, Cultuur 4654, Films 702, Jeugd 535, Activiteiten 100). **P1-42 (lijsten vol verlopen events) is daarmee feitelijk opgelost.** **Wat nog niet in de app komt: 622 van de 6.202 komende events (10%).** - **597 zonder categorie** — dat is 96% van het lek (zie taak 28, besluit 1). - Categorieën zonder display: **Dinnershow 18**, Lunch-concert/theater 5, Brunch 1, Tweedehands markt 1. **HTML-entiteiten (taak 29): titels en adressen zijn nu schoon** (0 van 6.202 komende events). `body` heeft er nog 2.570, maar die gaat in de app door de custom widget `FromHTML` en rendert dus goed. Enige rest: **55 records met een entiteit in `categorie`** (`Hiphop/Rap/R&B`, `DJ's`) — die staan letterlijk in het groene label. Kleine Drupal-fix op het categorie-veld van `flutterflow_events`. **Duplicaten (taak 30):** 2.095 nids komen meer dan één keer terug (2.142 overtollige rijen) — onveranderd, en nog steeds onzichtbaar in de app. *Meetscript: `fetch.py` + `analyse.py` (Nederlands én Engels datumformaat, jaar uit de weekdag) — stond in de scratchpad van deze sessie; recept staat in `CLAUDE.md`.* # ### 📌 Taak 30 · dubbele rijen in `flutterflow_events` — bekend, geen impact Nid **`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.