Pārlūkot izejas kodu

Audit-bevindingen opgelost: merkgroen + laatste 6 vertalingen

- categorielabel in de horeca-agenda van hardcoded #42E31C naar het
  thema-token Tertiary (#09B34A); projectbreed 0 treffers meer
- de zes ontbrekende EN-vertalingen in FilterBalkComponent ingevuld;
  0 van de 218 gerenderde i18n-sleutels mist nu nog Engels

Beide export-geverifieerd. CLAUDE.md: gedrag van het vertaal-globe-paneel
en de valkuil dat een chip-label tegelijk de doorgegeven waarde is.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 1 dienu atpakaļ
vecāks
revīzija
9571bf7659
2 mainītis faili ar 196 papildinājumiem un 50 dzēšanām
  1. 21 0
      CLAUDE.md
  2. 175 50
      TASKS.md

+ 21 - 0
CLAUDE.md

@@ -3639,6 +3639,27 @@ niet:
 3. Staat er al een Engelse vertaling, dan is dat vaak géén fout maar
    eerder werk — controleer met de export vóór je overschrijft.
 
+**Het vertaal-globe-paneel: na *Back* landt de EERSTE volgende globe-klik nooit,
+en twee globe-kliks in één batch openen en sluiten hem weer.** Bevestigd
+2026-09-20, zes vertalingen achter elkaar op `FilterBalkComponent`. Vaste
+werkwijze die elke keer werkte: **één klik per tool-aanroep**, met een `zoom` op
+de paneelkop erna om te zien of "Setting Translations" er staat — zo niet, klik
+dezelfde coördinaat nog eens in de vólgende aanroep. Het typen gaat daarna in
+twee aanroepen (`triple_click`, dán `triple_click` + `type`), zoals hierboven al
+beschreven. Bijvangst: een `FlutterFlowChoiceChips` heeft **per optie** een eigen
+globe naast het label "Option N", plus één bij **Initial Option** — die laatste
+moet je apart vertalen, anders matcht de voorgeselecteerde waarde in het Engels
+niet meer met een optielabel en begint de balk zonder selectie.
+
+⚠️ **Een chip-label is tegelijk de WAARDE die de app doorgeeft — check de
+consument vóór je vertaalt.** `FlutterFlowChoiceChips` zet `choiceChipsValue` op
+de gekozen *labeltekst*; op Home gaat die rechtstreeks naar
+`FFAppState().datumFilter`. Vertalen kan dus stil een filter breken. Hier kon het
+veilig omdat `filterDatumVan`/`filterDatumTot` op substring matchen en `today`
+al kenden. **En een lege `en` is geen "valt terug op NL"**: `getText()` geeft dan
+letterlijk een lege string, dus ook een label dat in beide talen hetzelfde is
+(`Weekend`) moet expliciet ingevuld worden.
+
 **Tree-zoekveld: de eerste klik op een gefilterd resultaat landt niet,
 de tweede wel.** Bevestigd 2026-09-08, consistent: na het typen in
 "Search for widget..." houdt dat zoekveld de focus, waardoor een klik

+ 175 - 50
TASKS.md

@@ -41,9 +41,14 @@ Google Play en één doorloop die alleen Bob kan doen.
 | **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** | Laatste vertalingen | Nog **precies 6** gaten in de hele levende app, allemaal in `FilterBalkComponent` — sleutels + voorgestelde EN staan in het audit-blok onderaan (2026-09-20). `tcyc3idz` bestaat niet meer |
+| **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 |
 
 ---
 #
@@ -246,6 +251,119 @@ bij inschrijving.
 **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/<nid>`).
+
+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,
@@ -323,6 +441,26 @@ Alleen de vijf app-views geven 200; `user_contents`, `places`, `_town_tree`,
 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
@@ -352,10 +490,11 @@ niet op de toegestane lijst", niet "display bestaat niet".
   `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 · laatste vertalingen.** `1k52hayg` → `End date` en `22xoyrqe` →
-  `Menu` zijn gedaan (2026-09-19 avond). **Restant, exact gemeten 2026-09-20:
-  zes sleutels in `FilterBalkComponent`** — zie het audit-blok onderaan dit
-  bestand. `tcyc3idz` bestaat niet meer in de export.
+- **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).
 
@@ -616,46 +755,31 @@ 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 2026-09-20 (Claude, browserloos)
-
-Verse export (`/tmp/ff-audit`) + de patroonchecks uit `CLAUDE.md`.
-**`fvm dart analyze lib/` op de verse export: 0 errors** (2331 info/warning).
-
-**Twee dingen om op te pakken — allebei builder-werk:**
-
-- 🟡 **Neongroen categorielabel in de horeca-agenda.** Eigenaar: Bob (of Claude,
-  na akkoord). `HorecagelegenheidEventTabelComponentCurrent` zet het
-  categorielabel op `Color(0xFF42E31C)` — een fel, ander groen dan het merkgroen
-  `#09B34A` dat `tagCategorieComponent` en `HorecagelegenheidoverzichtKaart`
-  gebruiken. Zichtbaar op elke zaakpagina met een gevulde agenda (testzaak nid
-  `75459`). Fix: die `Container` → Fill Color → thema-token **Tertiary**.
-  Export-check: `grep -n "0xFF42E31C" <widget>.dart` hoort leeg te zijn.
-  *(De roze `#F265A0` op de deelknop en de `#C8B1B1`/`#C28080` van Entree/Contact
-  stáán nog in de lokale working tree maar zijn in de builder al weg — die twee
-  waren dus terecht afgevinkt, de lokale export liep achter. Zie het punt over
-  stale mappen hieronder.)*
-
-- 🟡 **P1-17 is bijna klaar: nog precies ZES ontbrekende EN-vertalingen in de
-  hele levende app**, allemaal in de nieuwe `FilterBalkComponent` (taak 113).
-  Gemeten over alle 218 i18n-sleutels die in bereikbare widgets gerenderd worden;
-  alles daarbuiten is óf een dode kopie, óf EN==NL waar dat correct is
-  (`Home`, `Info`, `Website`, `Select...`, `Media`, `", "`).
-
-  | sleutel | NL | voorstel EN |
-  |---|---|---|
-  | `lv9f2gi8` | Zoek op titel | Search by title |
-  | `2dzc7lc6` | Alles | All |
-  | `mrkaqmb4` | Vandaag | Today |
-  | `kqsuit8r` | Weekend | Weekend |
-  | `ssmpizqo` | Deze week | This week |
-  | `u4ve4egb` | Alles | All |
-
-  ⚠️ `FilterBalkKanweg` en `FilterBalkComponentCopy` hebben dezelfde zes gaten —
-  die zijn wegwerp (taak 114d), niet vertalen.
-  ✅ **`tcyc3idz` bestaat niet meer** in de verse export; dat restpunt uit het
-  P1-17-blok hierboven kan weg.
-
-**Eén ding voor de git-kant:**
+### 🔍 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
@@ -663,8 +787,7 @@ Verse export (`/tmp/ff-audit`) + de patroonchecks uit `CLAUDE.md`.
   `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 bestanden — maar het maakt de standaard "0 errors"-check onbetrouwbaar en
-  het is precies het patroon dat eerder 161 errors gaf.
+  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) ·
@@ -676,9 +799,11 @@ Verse export (`/tmp/ff-audit`) + de patroonchecks uit `CLAUDE.md`.
 
 **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) ·
+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.
+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.