Bladeren bron

Sessieafsluiting 2026-08-31 avond: builderpatronen + merk-DNA bijgewerkt

CLAUDE.md aangevuld met wat deze sessie herbruikbaar opleverde:
- Issues-teller: een fout aanklikken navigeert naar de veroorzakende
  widget; de teller telt fouten van alle sessies op, dus een
  geblokkeerde export is vaak niet je eigen werk.
- List<String>-actieargument dat 'not set properly' meldt: Return Type
  openklappen forceert het wegschrijven. Werkt niet bij een lege
  binding.
- Tekstschaduw weghalen via de rij 'Shadow 1' -> link 'Remove'.
- De pagina-zoeklijst filtert trager dan je klikt; controleer de
  paginanaam voor je iets wijzigt.

Merk-DNA-passage geactualiseerd: het thema stond daar nog als
'FlutterFlow-fabrieksinstellingen' beschreven terwijl alle zes
merkkleuren er inmiddels in zitten en er in levende code geen
hardcoded rood meer staat.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 2 weken geleden
bovenliggende
commit
2f6aedbc71
2 gewijzigde bestanden met toevoegingen van 212 en 48 verwijderingen
  1. 118 3
      CLAUDE.md
  2. 94 45
      TASKS.md

+ 118 - 3
CLAUDE.md

@@ -875,6 +875,47 @@ swatch → wacht 8-10s → **screenshot** → pas dán op de themakleur klikken
 (`FlutterFlowTheme.of(context).<token>`); het hexveld erboven maakt juist
 weer een hardcoded kleur.
 
+**Een rode Issues-teller blokkeert élke export — en een fout aanklikken
+navigeert je rechtstreeks naar de widget die hem veroorzaakt.** Dat
+laatste is de snelste diagnose die er is en stond hier nog niet in:
+klik de rode teller rechtsboven → tabblad "Errors" → klik een regel →
+de builder springt naar de juiste pagina én opent het bijbehorende
+paneel (bij een actie zelfs de Action Flow Editor met de betreffende
+actie geselecteerd). Gebruik dit altijd vóór je zelf gaat zoeken.
+**Let op:** die teller telt fouten van álle sessies bij elkaar op, dus
+een geblokkeerde export is vaak niet jouw schuld — check eerst wíe de
+fout veroorzaakt voordat je in je eigen werk gaat spitten.
+
+**Een Custom-Action-argument van het type `List<String>` dat als fout
+"argument X is not set properly" gemeld wordt: klap in de Inline
+Function-dialoog "Return Type (Optional)" open en druk dan pas
+Confirm.** Bevestigd 2026-08-31 op `evenementCreate`'s `categorieTids`:
+de expressie was gewoon `<String>[]` en de dialoog meldde "No Errors",
+maar de fout bleef staan tot ik die sectie één keer had opengeklapt en
+opnieuw bevestigd — daarna viel de teller van 6 naar 5. Het
+openklappen lijkt het wegschrijven van het return-type te forceren.
+**Werkt niet als er nog helemaal géén binding staat** (waarde `UNSET`):
+een verse Inline Function aanmaken lukt in de UI, maar Confirm schrijft
+'m niet weg (5 pogingen, waarde bleef `UNSET`) — zelfde
+"ziet-er-opgeslagen-uit"-patroon als elders. Dan is de knop/actie
+weggooien en opnieuw opbouwen sneller dan doorprobéren.
+
+**Tekstschaduw van een `Text`/`AutoSizeText` verwijderen:** widget
+selecteren → sectie **"Shadows"** onderaan Text Properties → klik op de
+rij **"Shadow 1"** (niet op het chevron) → er verschijnt rechts in
+diezelfde rij een rode link **"Remove"** → klikken. Daarna staat er
+alleen nog "+ Add Shadow". Werkt betrouwbaar, 6x achter elkaar zonder
+falen.
+
+**De "Search pages or components..."-lijst filtert trager dan je klikt
+— wacht op een screenshot vóór je een zoekresultaat aanklikt.**
+Bevestigd 2026-08-31: na het typen van een nieuwe zoekterm staan de
+resultaten van de vórige zoekopdracht er nog even, en een klik op
+"de eerste regel" landt dan op de verkeerde pagina (in dit geval
+`kanwegHomeCopy` i.p.v. `PUitgaanPage`). Geen schade zolang je daarna
+de paginanaam in het rechterpaneel of de URL controleert — doe dat dus
+altijd vóór je iets wijzigt.
+
 **Een widget-kleur aan een thema-token binden (i.p.v. een hardcoded
 hex): klik het kleine kleurblokje vóór de kleurnaam, niet de tekst
 ernaast.** Klikken op de *tekst* ("Primary", of een hex) maakt er een
@@ -1436,6 +1477,68 @@ raken. Zelfde vuistregel: na 1-2 pogingen niet blijven proberen, aan
 Bob overdragen (zijn eigen browser/sessie kan een andere uitkomst
 geven, nog niet getest).
 
+**Een FlutterFlow API Call KAN een Drupal-sessiecookie meesturen — de
+aanname dat daarvoor `drupalRequest` nodig was, is onjuist.** Bevestigd
+2026-08-31 (live op toestel). Het ging altijd mis op de **syntax**: een
+headerwaarde `{{session_name}}={{sessid}}` is Postman-notatie, en
+`ApiManager` doet géén `{{ }}`-substitutie — die tekst wordt letterlijk
+verstuurd, Drupal ziet een anonieme bezoeker en geeft geen array terug,
+waarna `getJsonField(...) as List?)!` crasht. FlutterFlow wil **rechte
+haken**: `[session_name]=[sessid]`, wat in de export echte interpolatie
+oplevert (`'Cookie': '${sessionName}=${sessid}'`). **Er zijn twee
+kanten die allebei moeten kloppen — check ze samen in de export:**
+1. de call zelf: `'Cookie': '${sessionName}=${sessid}'` (niet `{{…}}`);
+2. de aanroepende Backend Query: `XCall.call(sessionName:
+   FFAppState().userSessionname, sessid: FFAppState().userSessionid)` —
+   ontbreken die argumenten, dan vallen ze terug op `''` en gaat er
+   `Cookie: =` mee, ook al is de header perfect.
+**Vuistregel wanneer wat:** lezen (GET, platte query-params) → gewone
+API Call, dan krijg je FutureBuilder/laadstatus/caching gratis.
+Schrijven met een **niet-platte body** (geneste structs, arrays), eigen
+foutafhandeling of logica vóór/ná het verzoek → custom action. Dát is
+de echte reden dat `stadsactiviteitCreate`/`bestandUpload` bestaan.
+
+**In een profile-build rendert een crashende subtree als een neutraal
+GRIJS BLOK, niet als het rode foutscherm — en alles eronder in dezelfde
+Column verdwijnt mee.** Bevestigd 2026-08-31: één kapotte dropdown
+bovenin maakte vier widgets eronder (incl. de verzendknop) onzichtbaar,
+wat de indruk gaf dat ze nooit gebouwd waren. Zie je zo'n grijs vlak:
+`adb logcat`/de run-log heeft de echte stack trace (let op: slechts één
+volledige dump per proces, zie elders in dit bestand). Voor dit soort
+speurwerk is een **debug**-build (`ff-run-fvm.sh`) juist prettiger dan
+profile — daar krijg je het rode scherm mét bestandsnaam en regelnummer.
+
+**Een datumpicker op een `readOnly` TextField: de actie moet op een
+omhullende Container's On Tap, niet op de TextField zelf.** Een
+TextField biedt in FlutterFlow alleen On Submit / On Change / On Focus
+Change — géén On Tap. Op "On Submit" (`onFieldSubmitted`) gaat de
+picker nooit af, want een readOnly veld kan niet ingediend worden.
+Werkend recept: **Wrap Widget → Container**, actieketen daarheen
+verplaatsen ("Copy Action Chain" → "Paste Action(s)"), oude On
+Submit-keten verwijderen, en op het TextField **Enabled uit** — een
+readOnly veld vangt de tap namelijk zélf op en geeft 'm niet door aan
+zijn ouder; pas een *disabled* veld laat 'm door. `readOnly` mag aan
+blijven. Let bij het plakken op de bekende hernummer-valkuil: de laatste
+actie las `datePicked0` terwijl de keten `datePicked1` vulde → harde
+compile-error.
+
+**Een conditie op een JSON-veld dat geen echte bool is, genereert
+`if (getJsonField(...))` zonder null-check — compileert wél, crasht bij
+runtime.** Bevestigd 2026-08-31: een Action-Flow-conditie op `$.nid`
+gaf een bool-cast op een String/`null` (`dart analyze` ziet dit niet,
+want `getJsonField` levert `dynamic`). Kies een sleutel die écht een
+bool is (bv. `$.success`), of bouw een Single Condition met een
+expliciete operator. Bij een **Visibility**-conditie genereert "Is Set"
+wél netjes `!= null` — het verschil zit in waar de conditie staat.
+
+**Een widget kan zijn EIGEN Backend-Query-response niet gebruiken in
+zijn eigen Visibility-conditie — wel in zijn Options.** De
+Set-from-Variable-dialoog toont die response daar simpelweg niet als
+bron. Oplossing: **Wrap Widget → Column**, de Backend Query naar die
+wrapper verplaatsen (en van het kind verwijderen), daarna is de
+response in scope voor zowel de Options als de Visibility van het kind.
+Bevestigd 2026-08-31 op de stadsrechten-shortcut.
+
 **API-call headers: check op hardcoded literals i.p.v. `[varname]`
 templates.** Werkt een call via curl wél maar vanuit de app/Response &
 Test-panel niet: check het Headers-tabblad — een handmatig ingeplakte
@@ -1658,9 +1761,21 @@ exacte veldnaam+type-specificatie (kost hem seconden per veld).
   `0 7px`), geen pil; Font-Awesome-icoon vóór datum en adres;
   kruimelpad op elke detailpagina. **Vuistregel bij twijfel: rood is
   merk en chroom, oranje is wat je aanklikt om verder te komen** — de
-  site gebruikt rood nóóit als knopkleur. Het app-thema staat nog op
-  FlutterFlow-fabrieksinstellingen (`primary` #4B39EF); zie P1-32 t/m
-  P1-34 en het Designbrug-rapport in `TASKS.md` voor de mapping.
+  site gebruikt rood nóóit als knopkleur.
+
+- **Het app-thema draagt dit merk-DNA inmiddels volledig** (P1-32 t/m
+  P1-34, P1-39 en P1-40 afgerond 2026-08-31). `LightModeTheme` in
+  `lib/flutter_flow/flutter_flow_theme.dart`: `primary` #9A141D
+  (logo-rood), `secondary` #FF680D (oranje/actie), `tertiary` #09B34A
+  (groen/categorie), `primaryText` #3E454C (leisteen), `alternate`
+  #EEEEEE, `primaryBackground` #F2F2F2. Alleen #EE3338
+  (locatiespeldje) heeft nog geen eigen token; de vier Accent-slots
+  staan nog op FlutterFlow-fabriekswaarden en zijn dus vrij als je er
+  een nodig hebt (Theme Settings → Colors → "+ Add Color" maakt ook een
+  eigen benoemde ingang). **In levende code staat geen hardcoded rood
+  meer** — wat er nog aan `#B1061E`/`#B50808`/`#B12727` in de export
+  zit, zit uitsluitend in dode kopieën (`_copy`, `_copy2`,
+  `copymethartje`, `kanweg/*`) en wacht op P2-7's opruimactie.
 
 
 - **Firebase App Check beschermt alleen Firebase-diensten zelf

+ 94 - 45
TASKS.md

@@ -3099,55 +3099,104 @@ een volgende sessie niet opnieuw hoeft te reconstrueren:
 
 **Overige restpunten (sessie 2026-08-31, live pair-fix):**
 
-8. **`field_town_access` bevat nog plaats-niveau tids (Drupal-kant).**
-   De omzetting naar gemeente-niveau (2026-08-27) wijzigde alleen de
-   widget-instellingen, niet de al opgeslagen waarden — bevestigd via
-   `mijn_stadsrechten` op Bob's eigen account (`tid` 29946
-   = 's-Gravendeel, een Plaats; `parent_titel` = Binnenmaas, de
-   gemeente). Gevolg: de rechten-shortcut op `stadsactiviteitAanmaken`
-   zet `createGemeenteID` op een plaats-tid en de Plaats-dropdown
-   blijft leeg. **De app-binding (`$[:].tid`) is correct; de data niet.**
-   Eenmalige drush-migratie nodig voor alle gebruikers met
-   `field_town_access`: tids waarvoor
-   `count(taxonomy_get_parents_all($tid)) === 3` vervangen door hun
-   gemeente-parent (`$parents[1]->tid`), dedupliceren, `user_save()`.
-   Eerst devbob, dan productie. Script is in de sessie aan Bob gegeven.
-10. **Basis-URL centraliseren — Fase A af, Fase B open (app-breed, niet
-    alleen deze pagina; overweeg een eigen taak-ID).** Aanleiding: op
-    devbob kunnen testen zonder overal URL's te wijzigen. **Gedaan:**
-    nieuwe App State-variabele **`apiBaseUrl`** (default
-    `https://uitgaanskrant.com`), en alle vier de custom actions
-    (`stadsactiviteitCreate`, `bestandUpload`, `drupalRequest`,
-    `drupalLogin`) bouwen hun URL nu daaruit op. Ze zijn **tolerant**:
-    een argument dat met `http(s)://` begint wordt ongewijzigd gebruikt,
-    anders wordt het als pad achter `apiBaseUrl` geplakt — dus alle 26
-    bestaande aanroepen met een volledige URL blijven werken en kunnen
-    in eigen tempo ingekort worden tot een pad. Truc: de parameter zelf
-    wordt overschreven (`url = ...`), zodat verderop in de action niets
-    aangepast hoefde te worden. **Let op:** custom *Functions* kunnen
-    `FFAppState()` NIET lezen (`custom_functions.dart` importeert
-    `app_state.dart` niet en dat importblok is niet bewerkbaar) —
-    custom *Actions* wel. **Nog open (Fase B):** de 13 top-level API
-    Calls in `api_calls.dart` hebben nog een hardcoded host (16
-    plekken). Centraliseren betekent ze in de bestaande API Group
-    onderbrengen (nu `ProductieGroup`, base-URL-veld) — check eerst of
-    het ⋮-menu van een API Call een "Move to group" heeft; zo niet moet
-    elke call opnieuw aangemaakt worden ín de groep en moet **elke
-    Backend Query die 'm gebruikt opnieuw gebonden** worden (het dure,
-    foutgevoelige deel). Hernoem bij die gelegenheid de groep
-    `productie` → `drupal`/`backend`, want de naam klopt niet meer zodra
-    hij naar devbob wijst.
-11. **Devbob zit achter HTTP Basic Auth** — alleen `apiBaseUrl` omzetten
-    naar devbob geeft op álles een 401. Er is een
-    `Authorization: Basic <base64>`-header nodig die meegaat wanneer de
-    base-URL devbob is: in de custom actions één `if` erbij, voor de API
-    Calls een group-level header (extra argument om Fase B te doen).
-    Nog niet gebouwd.
-
+8. **`field_town_access` staat volledig op plaats-niveau — 9.292 rijen
+   over 6 accounts (Drupal-kant).** Gemeten 2026-08-31 via SQL:
+   Bee 5200, Disco-Party.nl 2317, bobcity 1254, nomad 517, bobhoreca 2,
+   Team Klein Frankrijk 2 — alles niveau 3 (Plaats). De omzetting naar
+   gemeente-niveau (2026-08-27) wijzigde alleen de widget-instellingen,
+   niet de opgeslagen waarden. **`mijn_stadsrechten` is NIET stuk** —
+   het endpoint geeft correct terug wat er staat (bevestigd: API-length
+   1254 == SQL-count voor bobcity).
+   **Correctie op een eerdere notitie in deze taak:** de conclusie
+   "bobcity heeft 's-Gravendeel als stadsrecht" was gebaseerd op het
+   eerste item van een `head -20` over de volledige, alfabetisch
+   gesorteerde lijst — geen bewijs van iets. De omvang bleek pas via
+   de database.
+   **Migratiescript is aan Bob gegeven** (dry-run + `$APPLY`-vlag,
+   haalt de hele hiërarchie in 2 queries op i.p.v.
+   `taxonomy_get_parents_all()` per term; plaats-tid → gemeente-ouder,
+   dedupliceren, `user_save()`). Nog niet gedraaid. Eerst devbob.
+   **⚠️ Productbeslissing die eerst gemaakt moet worden:** na conversie
+   houdt bobcity ~300 gemeenten en Bee mogelijk heel Nederland — dan is
+   de "Mijn gemeenten"-shortcut geen snelkoppeling maar een tweede
+   plaatsenlijst. Ofwel de landelijke scope terugbrengen tot wat die
+   accounts echt beheren, ofwel de shortcut anders vormgeven (alleen
+   tonen bij ≤ ~25 gemeenten, of een zoekveld i.p.v. een dropdown).
+   Zonder die keuze heeft de migratie weinig zin.
 9. **Categorie-dropdown toont de hele hiërarchie plat.** `categorieen`
    geeft `diepte`/`parent_tid` mee; de multiselect toont nu ouder- én
    kindtermen door elkaar zonder inspringing. Optionele verfijning.
 
+**Live test 2026-08-31 — pagina werkt grotendeels, drie bugs gevonden
+en gefixt, twee open.**
+- **Gefixt: sessie-cookie ging nooit mee.** Vijf API Calls hadden
+  `Cookie: {{session_name}}={{sessid}}` — Postman-syntax, die
+  FlutterFlow niet substitueert (`ApiManager` doet geen `{{ }}`-
+  vervanging, stuurt de tekst letterlijk). Moet `[session_name]=[sessid]`
+  zijn. **Hiermee is ook de aanname weerlegd dat `drupalRequest` nodig
+  was omdat API Calls geen cookie konden meesturen** — dat kan wel; het
+  ging destijds mis op de syntax. Zie de nieuwe regel in `CLAUDE.md`.
+  Gefixt: `Entreeopties`, `Categorieen`, `MijnStadsrechten`.
+  **Nog open: `MijnHorecagelegenheden` (regel ~1096) en
+  `FavorietenAgenda` (regel ~131)** — die staan nog op `{{…}}`, dus
+  `mijnProfiel`'s horeca-sectie en Favorieten Tab 1 zijn vermoedelijk
+  al die tijd leeg. (`FavorietenAgendaTESTKANWEG` heeft een hardcoded
+  sessie-cookie; dode call, zie P2-7.)
+- **Gefixt: datumpickers vuurden nooit.** Beide datumvelden hadden hun
+  picker op **On Submit** (`onFieldSubmitted`) terwijl het veld
+  `readOnly` is — dat kan per definitie niet afgaan. Een TextField heeft
+  in FlutterFlow geen On Tap-trigger; oplossing: **Wrap Widget →
+  Container**, actieketen op de Container's On Tap, en op het TextField
+  **Enabled uit** (een `readOnly` veld vangt de tap zelf op en geeft
+  'm niet door aan de ouder).
+- **Gefixt: crashes op lege API-responses.** `DropDownGemeente`,
+  `DropDownPlaats` en de verzendknop hebben nu Visibility-guards
+  (`createProvincieID` / `createGemeenteID` / `createPlaatsID` Is Set).
+- **Open: geen terugkoppeling na het kiezen van logo/foto's.** Plan
+  staat klaar: `bestandUpload` geeft naast `fid` ook een `url` terug;
+  nieuwe Page State `createLogoUrl` (String) + `createFotosUrls`
+  (List<String>), gevuld met `$.url` in dezelfde Update-Page-State-actie,
+  daarna een Image (logo) en een Wrap met Generate Dynamic Children
+  (foto's). **Blokkade:** de "Set from Variable"-dialoog van de Image's
+  **Path** toont alle losse String-variabelen gedimd en laat alleen
+  `List<String>`-velden selecteren. Debugplan staat hieronder.
+- **Open: categorie-multiselect gefilterd op subniveau.** Bindingen
+  staan nu op `$[?(@.diepte == 1)].tid` / `.naam` (diepte 0 = de 7
+  hoofdcategorieën, diepte 1 = de subcategorieën, bevestigd via curl).
+  **Nog niet live geverifieerd** of FlutterFlow's `getJsonField` deze
+  JSONPath-filtersyntax aankan. Faalt hij, dan een custom function met
+  platte Map-toegang (`categorieVeldOpDiepte(respons, diepte, veld)`),
+  of een `?diepte=1`-parameter op het Drupal-endpoint.
+
+**Debugplan voor de Image-Path-blokkade (eerste stap volgende sessie):**
+1. **Zoekbalk gebruiken.** Typ `createLogoUrl` in "Search variables..."
+   bovenin de dialoog. Deze keuzelijst rendert in dit project vaker
+   items als niet-klikbaar terwijl ze het wel zijn; de gefilterde lijst
+   werkt dan meestal gewoon. Kost 5 seconden, probeer dit eerst.
+2. **Type van de variabele controleren.** Pagina-root → Page State
+   Variables → `createLogoUrl` moet **String** zijn met **Is List uit**.
+   Staat er iets anders, dan is dat meteen de verklaring.
+3. **Tegenproef.** Probeer in dezelfde Path-dialoog een ándere losse
+   String te kiezen (bv. `createLogoFid`). Lukt dat ook niet, dan is
+   het een typefilter op het Path-veld en niet iets aan `createLogoUrl`.
+   Lukt het wél, dan is er iets mis met die ene variabele.
+4. **Tegenproef 2.** Bind de Path aan een App State-String (bv.
+   `userName`). Werkt dát, dan accepteert het veld wél Strings maar
+   geen *Page* State — een scope-probleem, geen typeprobleem.
+5. **Omweg als 1-4 niets opleveren:** custom function
+   `String logoPad(String? url) => url ?? '';` (plat, geen closure,
+   Ctrl+S) en de Path binden aan **Custom Functions → `logoPad`** met
+   `createLogoUrl` als argument. Dat patroon werkt in dit project
+   bewezen om typeblokkades in bindingsdialogen te omzeilen (zie
+   `favorietenBodyNode`).
+6. Werkt de binding, vergeet dan niet de **Visibility → Conditional**
+   op `createLogoUrl` "Is Set and Not Empty" — anders staat er vóór het
+   uploaden een kapotte-afbeeldingsplek. En haal het testplaatje
+   (`https://picsum.photos/seed/489/600`) uit het Path-veld.
+7. Voor de foto's geen losse Image maar een **Wrap** met Generate
+   Dynamic Children over `createFotosUrls` — daar is `List<String>`
+   juist wél het gevraagde type.
+
 **Losse observatie (niet blokkerend):** FlutterFlow's Issues-paneel
 toont 1 error "Property Override — return type mismatch" wijzend naar
 `DropDownProvincie`, terwijl de export 0 Dart-errors geeft en de