# TASKS — alleen nog OPEN taken *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. --- ## 📍 Waar staat het project? 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. ## 👤 CLAUDE — OPEN | # | Taak | Kort | |---|---|---| | **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 | ## 👤 BOB — OPEN, in volgorde van wat de livegang blokkeert | # | 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 | | **115** | AdBanner verplaatsen op `PUitgaanPage` | Onder de slider, bóven zoekveld/datum/chips. Eigen blok hieronder | | **116** | AdBanner springt bij het laden | Oorzaak gevonden in de gegenereerde widget; fix is een vaste hoogte. Eigen blok hieronder | | **117** | Kalenderpermissie eruit | De app heeft 'm niet nodig; scheelt een permissie in de Play-listing. Eigen blok hieronder | | **118** | AdMob EU-consentbericht | De app roept UMP al aan, maar het bericht moet in de AdMob-console gepubliceerd zijn. Eigen blok hieronder | | **119** | Besluit: App Links voor `https://` | Gedeelde links openen nu altijd de browser, nooit de app. Post-launch keuze | --- # ### 🔧 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.)* - ⬜ **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. --- ## 🅖 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. - [x] **G0.3 — ✅ BESLOTEN (Bob, 2026-09-19): alles via FlutterFlow Deploy.** Lokaal bouwen is afgevallen: het heeft twee stille faalmodi die allebei al zijn opgetreden (Flutter-vogeltje als icoon zonder `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. - [x] **G0.4 — ✅ BESLOTEN (Bob, 2026-09-19): alleen Android, iOS later.** **Bob 2026-09-19, definitief:** *"fuck ios. we gaan eerst een android app bouwen. Als die geld oplevert, gaan we naar apple."* Niet opnieuw agenderen; wat het zou kosten staat in het blok "iOS — wat er extra bij komt". - [x] **G0.5 — ✅ CONCEPT KLAAR (Claude, 2026-09-19), akkoord Bob.** Drie varianten korte omschrijving (70/75/71 tekens) + lange omschrijving (1585 van 4000), opgebouwd uit de echte functielijst (drawer + Home-tabs). Bob's aanvullingen verwerkt: account is **gratis, geen in-app aankopen**; de formulering "horecaondernemers en stadsredacteuren" blijft staan. 📁 **Tekst staat in `~/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). - [x] **G0.6 — ✅ NIEUWE SET KLAAR (Claude, 2026-09-19 avond).** Alle tien opnieuw geschoten uit een verse `--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. - [x] **G0.7 — ✅ KLAAR (Claude 2026-09-20, variant B gekozen door Bob).** 📁 `~/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**. # ### 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. # ### 🍏 iOS — wat er extra bij komt (Bob's vraag 2026-09-19; besluit: NIET nu) **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:** - **D-U-N-S-nummer** — Apple eist dat voor een organisatie-account, net als Google. Dat is de langste stap en die is bij jou al gezet. - **De permissieteksten in `Info.plist`** staan al goed (Camera, PhotoLibrary, Calendar) — geverifieerd 2026-09-19, precies 3 keys. **Wat er nieuw bij komt:** 1. **Apple Developer Program-account** (~$99/jaar) + verificatie van de organisatie. 2. **Bundle ID + certificaten/provisioning** in App Store Connect. Er is nu *niets* voor iOS ingericht: alleen `ios/Runner.xcodeproj/project.pbxproj` beweegt mee in elke export. 3. **Eigen screenshots in Apple-formaten** (iPhone-maten + iPad) — andere afmetingen dan Play, dus die van G0.6 zijn niet herbruikbaar. 4. **Privacy Nutrition Labels** — Apple's tegenhanger van Data Safety, apart invullen. 5. ⚠️ **App Tracking Transparency (ATT).** AdMob gebruikt op iOS de IDFA. Wil je gepersonaliseerde advertenties, dan moet er een ATT-prompt komen bovenop de bestaande GDPR-consentflow. Dat is echt extra bouwwerk, geen vinkje. 6. ⚠️ **Guideline 5.1.1(v): account verwijderen moet IN de app kunnen.** Onze knop (taak 85) opent een webpagina. Voor Play is dat prima; Apple is daar strenger over en dit is een klassieke reden voor een afwijzing. Reken op een echte in-app verwijderroute vóór een iOS-inzending. 7. **Guideline 4.2 (minimum functionality)** — apps die vooral webcontent tonen worden geweigerd. Deze app heeft genoeg eigens (favorieten, aanmelden, agenda-integratie, offline-melding), dus dat risico is klein, maar het is wel waar Apple naar kijkt. **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. ### 115 · AdBanner verplaatsen op `PUitgaanPage` · Eigenaar: Bob **Wens (Bob 2026-09-20):** de banner onder de slider en **boven** het zoekveld, de datumkiezer en de choicechips. **Stand nu** (verse export 2026-09-20, `lib/uitgaanspaginas/p_uitgaan_page/p_uitgaan_page_widget.dart:164-230`), body-`Column` met vier kinderen: 1. `PUitgaanSliderComponent` — Visibility `!FFAppState().zoekOpen` 2. `Expanded > FilterBalkComponent` — zoekveld, datum, chips 3. `Padding(top 15) > FlutterFlowAdBanner` ← moet naar plek 2 4. `Expanded > Padding > Container > PUitgaantabelKaartComponent` **Route: twee sleepacties.** Een drop op een container-rij landt altijd op **positie 0**, dus in omgekeerde volgorde slepen: 1. Klap alle vier de kinderen in (chevrons), selecteer de body-`Column`-rij één keer en druk `Escape` — een sleep landt alleen als de doelrij vooraf geselecteerd is. 2. Sleep de **`Padding`-rij met de AdBanner** op de **`Column`-rij** → `[banner, slider, filterbalk, lijst]`. 3. Sleep de **slider-rij** op de **`Column`-rij** → `[slider, banner, filterbalk, lijst]` ✅ Eén sleep per keer, met een verse `zoom` op de boom ertussen (de rijen verschuiven). De Visibility-conditie op de slider overleeft een sleep. **Let op:** de twee horeca-overzichtspagina's hebben hun banner **onderaan** de Column staan. Die is niet meeverplaatst — apart besluit als je ze gelijk wilt trekken. ### 116 · AdBanner "opent groot en wordt dan klein" · Eigenaar: Bob **Geen AdMob-kwestie — het zit in `lib/flutter_flow/flutter_flow_ad_banner.dart` (gegenereerd, dus niet lokaal te repareren).** Vóór het laden rendert die widget een zwarte `Container` **zonder eigen hoogte** met een laadtekst erin; die tekst is extra lang zolang `showsTestAd: true` (er komt een waarschuwingsalinea bij). Op 411 dp is dat ruwweg 150 dp hoog. Zodra de banner binnen is wordt de Container exact zo hoog als de banner (~50 dp). Vandaar de sprong — en de `Expanded`-blokken eromheen krimpen en groeien mee, dus de hele lijst verspringt. Twee dingen volgen daaruit: het wordt **vanzelf minder** zodra *Show Test Ads* uit gaat (kortere laadtekst), en het verdwijnt **niet** vanzelf — bij een uitblijvende fill blijft die placeholder gewoon staan. **Fix: geef de banner een gereserveerde hoogte.** - Rechtsklik de AdBanner → **Wrap Widget → Container**, geen Fill Color. - Op die Container **Height** als **Responsive Value**: `50 / 50 / 90 / 90`. Dat volgt Google's anchored-adaptive-hoogtes (<400 dp → 32, 400-720 → 50, >720 → 90); op een tablet wil de banner dus echt 90. - **Ad Banner Dimensions leeg laten** (= adaptive). Dat is bewust anders dan de twee horeca-pagina's, die `width: schermbreedte, height: 50` hebben staan: dat is een *custom* AdSize en die vult AdMob beduidend slechter dan een standaard/adaptive formaat. Nameten zodra er echte advertenties draaien. **Terugvaloptie als de Responsive-Value-dialoog tegenwerkt:** vaste Height 50 en accepteren dat de tabletbanner 40 dp krapper staat dan hij wil. ### 117 · Kalenderpermissie uit het manifest · Eigenaar: Bob **De app vraagt `READ_CALENDAR` en `WRITE_CALENDAR` aan zonder ze te gebruiken.** Nagemeten 2026-09-20: - `zet_in_agenda.dart:51` gebruikt `Add2Calendar.addEvent2Cal(...)`. Dat is de **intent**-route (`ACTION_INSERT`) en die vraagt het toestel om de agenda-app te openen — daar is geen enkele permissie voor nodig. - De plugin declareert ze zélf niet: `~/.pub-cache/.../add_2_calendar-3.0.1/android/ src/main/AndroidManifest.xml` is een **leeg** manifest. - Ze komen dus uit **App Settings → Permissions** (aangezet bij taak 98) en staan in de export op `android/app/src/main/AndroidManifest.xml:8-9`. **Waarom het uitmaakt:** Play toont "Agenda" bij de app-permissies, en dat is een van de dingen waar mensen op afhaken bij een uitgaansapp. Bovendien scheelt het een rij in het Data safety-formulier (G2.7). **Doen:** App Settings → Permissions → **Calendar uit** → verse export → de twee regels moeten uit het manifest zijn → daarna de agendaknop op `EventCurrent` één keer testen op een echt toestel (de AVD's kunnen dit niet, hun AOSP-kalender is kapot — zie `CLAUDE.md`). ⚠️ **Bij de iOS-ronde terugzetten.** Op iOS gebruikt `add_2_calendar` EventKit en daar is de permissie + `NSCalendarsUsageDescription` wél nodig. Nu Android-only is dat geen bezwaar. ### 118 · AdMob: is het EU-consentbericht gepubliceerd? · Eigenaar: Bob 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. ### 119 · Besluit: App Links voor `https://uitgaanskrant.com` · Eigenaar: Bob Het manifest heeft één intent-filter, op het **custom scheme** `uitgaanskrant://uitgaanskrant.com` (`AndroidManifest.xml:47-52`). Er is géén filter voor `https://`. Gevolg: een uitgaanskrant.com-link die iemand in WhatsApp of een mail aantikt opent **altijd de browser**, nooit de app — ook de link die de deelknop zelf verstuurt (`https://uitgk.com/`). Dat is nu geen fout, maar wel een gemiste kans zodra de app live staat. Het vergt een `assetlinks.json` op de domeinen plus een tweede intent-filter, en dus een bewust besluit. **Post-launch, geen blokker.** (Terzijde: `android:autoVerify="true"` staat nu op het custom-scheme-filter en doet daar niets — autoVerify geldt alleen voor http/https. Onschadelijk.) ### 65 · Release-build — wat er NOG getest moet worden · Eigenaar: Bob **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). ### 📌 Livegang-checks die AL goed zijn (gemeten 2026-09-19, niet opnieuw doen) - **De deel-URL werkt.** `https://uitgk.com/` (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. - **De support-URL's geven 200**: `/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). ## ✅ API-oppervlak dichtgezet — KLAAR (2026-09-20) **Fase 1 én 2 staan op productie en zijn nagemeten.** Van 42 naar **22** methodes op endpoint `flutterdrup`. Het bewezen lek is weg: `node/.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. ### 📌 Views-resource: al beperkt, niets te doen 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". ### 📌 Livegang-checks die AL goed zijn — aanvulling 2026-09-20 (Claude) Alle vijf nagemeten op de verse export en op productie; **niets te doen**, hier genoteerd zodat niemand het opnieuw uitzoekt: - **16 KB page size** (Play-eis voor nieuwe apps sinds 1 nov 2025): **in orde.** Gemeten op `~/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. - **targetSdk**: `36` (`android/app/build.gradle:62`), ruim boven de Play-eis van 35. - **Het privacybeleid dekt de app-specifieke verwerking.** `/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. - **De twee uitgaande gebruikers-URL's geven anoniem 200**: `/nl/user/register` (de knop *Account aanmaken* op Favorieten) en `/nl/user/password`. Zelfde val als bij G2.2, maar hier dus goed. - **De UMP-consentcode zit in de app** (`main.dart:32`) — of het bericht ook echt verschijnt hangt af van de AdMob-console, zie taak 118. ## 🚦 Bij livegang — schakelaars en opruimklussen · Eigenaar: Bob - **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.)* ### P2-46 · EventCurrent: restpunten na de ombouw van 2026-09-19 · Eigenaar: Claude **Gedaan (export- en toestel-geverifieerd, profile-build op emulator-5554):** backups `EventCurrentCopy`, `EvenementHorecagelegenheidCopy`, `EvenementInfoCopy`; event-kaart met logo 96×96 links en `EvenementInfo` (Expanded) + Maps-knop rechts; de 500 px-Container weg; `EvenementHorecagelegenheid` zonder tabs: `RowLogo` (logo 96×96 + `ColumnRechts`: titel, labels, adres/plaats, telefoon, e-mail), links in een `Wrap` (Menukaart/Website/Facebook/Instagram/X/"Op Uitgaanskrant.com" met host ervoor), `TextInhoud` (zaakbeschrijving), foto's, `ColumnOpening` en `ColumnBezorgen` met Visibility op `openingstijden`/`bezorgtijden`; e-mail leest nu `$[:]['e-mail']`; drie `phone: false`-vlaggen weg; telefoon-guard zonder `valueOrDefault`; Entree-blok alleen bij `entreeprijs`; roze/rode vulkleuren van Entree/Contact weg; EN-vertalingen "Opening hours"/"On Uitgaanskrant.com". **Nog te doen:** - ✅ **Laatste toestelronde gedaan (2026-09-19, profile op emulator-5554, `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). - ✅ EN-vertaling van "Menukaart" (`22xoyrqe`) staat nu op **`Menu`** (2026-09-19 avond). - Niet getoond: `kvk` (zaak). Bewust overgeslagen. - ✅ **`organisator` wordt nu wél getoond (2026-09-19, Bob's besluit, af en op toestel geverifieerd).** Zie het eigen blok hieronder; taak 99 kan hiermee dicht. - Bob: de drie backup-kopieën (`EventCurrentCopy`, `EvenementHorecagelegenheidCopy`, `EvenementInfoCopy`) op de P2-7-opruimlijst zetten zodra de nieuwe pagina goedgekeurd is. Claude verwijdert niets. ## 🗄 Drupal / views — open # ### 📌 Het horeca-overzicht laadt AL lazy — één fetch bij openen, niet zes **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. - **On Page Load** (regel 55, `addPostFrameCallback`) doet er **één** — tab 0 (`horcat 17969`, Activiteiten, `services_1`). - De **TabBar** heeft `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.** ### P2-24 · Restpunten horeca-zoekfilter · Eigenaar: zie per punt **A, C, D en E zijn af** (2026-09-19, export-geverifieerd): alle zes de `StaggeredView`s 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. ### 🔴 Taak 28 / 109 · 618 toekomstige events zonder categorie komen NERGENS in de app · Eigenaar: Bob **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. ## 📚 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-19 (Claude) | | 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. ### 📌 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. ### 🔍 Pre-livegang code-audit — UITGEVOERD EN AFGEWERKT 2026-09-20 (Claude) 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: - ✅ **Neongroen categorielabel in de horeca-agenda gefixt.** De `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. - ✅ **P1-17 is rond voor de app: 0 ontbrekende EN-vertalingen.** De zes gaten in `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-**label**tekst is tegelijk de waarde die in `FFAppState().datumFilter` belandt. **Blijft open (niet van Claude):** - 🟠 **De lokale repo loopt achter op de builder: 15 mappen onder `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.