Werk uitsluitend binnen deze projectdirectory
(/home/bob/Projects/ff-app/uitgaanskrant-1qhvtd). Niet daarbuiten
zoeken of scannen — scope alle bestandsoperaties tot dit project.
CLAUDE.md + TASKS.md
zijn samen het volledige geheugen tussen sessies.TASKS.md-status kan achterlopen op de echte code (Bob werkt
gelijktijdig, en documentatie-updates lopen niet altijd synchroon
met de export). Check een taak die er al even staat met
git log --oneline -S"<kenmerkende string/tekst>" of een gerichte
grep vóórdat je 'm oppakt — niet blind vertrouwen dat "open"
betekent "nog niet gefixt". (Precedent: 2026-08-04 bleken 2 "open"
P0-taken al op 2026-08-02 gefixt te zijn, git-bevestigd.)TASKS.md heeft een stabiel ID (bv. P0-1) en een
Eigenaar:-regel — Bob (sneller/simpeler voor hem zelf, meestal
builder-UI met een bekend fragiele dialoog, zie hieronder), Claude
(onbeklaimd, vrij op te pakken), of ... — bezig (iemand is er
nu actief mee bezig). Vuistregel voor wie een nieuwe taak zou
moeten doen: een kort, mechanisch herhaald patroon zonder geneste
dialogen → Claude; ConditionalBuilder/JSON-Path-condities,
List-typed function-argumenten, of iets dat eerder al vastliep →
Bob.flutter run-sessie starten om een stack
trace te vangen), niet alleen voor builder-edits. (Precedent
2026-08-04: twee sessies pakten onafhankelijk dezelfde P0-taak op —
Bob moest scheidsrechteren tussen twee stukken werk aan hetzelfde
bestand. Tweede precedent, 2026-08-09: een sessie deed 20+
minuten actief P1-13-onderzoek — eigen flutter run-proces + een
nieuwe bug gevonden (P1-19) — zonder ooit "— bezig" te zetten; een
nieuwe sessie kwam er via git diff/proces-inspectie toevallig
achter vlak vóórdat ze zelf hetzelfde pad zou inslaan. Geen schade,
maar puur geluk — vandaar nu ook ff-session-check.sh, zie
hieronder. Derde precedent, 2026-08-21: tijdens P1-26 (gemeente-
hartje op HeaderButtonsComponent + het bijbehorende Drupal-
endpoint) bleek een andere sessie al actief te bouwen — pas ontdekt
via een onverwachte git status-diff (een halfklare
print('IconButton pressed ...')-placeholder in de working tree),
niet via een "— bezig"-marker in TASKS.md, want die was ook nu
weer nooit gezet. Geen schade (beide sessies' werk sloot uiteindelijk
netjes op elkaar aan), maar het bevestigt dat de regel hierboven in
de praktijk niet gevolgd wordt.**Edit naar TASKS.md op schijf, uitgevoerd zodra je een taak
serieus oppakt (vóór je in de builder/code begint), niet pas bij
de afsluitroutine aan het eind van de sessie. Sessies delen dezelfde
working directory op Bob's machine, dus een geschreven
bestandswijziging is voor een andere sessie direct zichtbaar
zonder dat er gecommit hoeft te worden — een aantekening die pas bij
sessie-einde geschreven wordt, is voor niemand anders zichtbaar
precies op het moment dat het er het meest toe doet. Symmetrische
plicht bij het oppakken van een taak: check ook zelf eerst — vóór je
begint — of de Eigenaar-regel al "— bezig" zegt (verse
ff-session-check.sh of een gerichte grep op de taak-ID/component-
naam), niet er blind van uitgaan dat de lijst nog klopt sinds je 'm
voor het laatst las./home/bob/Projects/ff-session-check.sh
(géén argumenten nodig voor dit project) — bundelt in één keer
git-status, welke devices/emulators draaien, welke
flutter/ff-run-fvm/script-processen actief zijn (met leeftijd), en
welke TASKS.md-taken al "— bezig" staan. Vervangt de losse
handmatige git status/adb devices/ps aux-stappen hieronder —
dat was voorheen makkelijk (deels) over te slaan, dit script niet.
Een draaiend ff-run-fvm.sh/flutter run-proces alléén is géén
betrouwbaar signaal dat er "nu" iemand actief mee bezig is —
bevestigd 2026-08-07: meerdere van die processen bleken dagen oud
(gestart Aug05/Aug06, nog steeds draaiend), puur omdat het
interactieve hot-restart-loop-karakter ze nooit vanzelf afsluit. Het
betrouwbaardere signaal voor "hier ligt nog niet-afgerond werk": een
niet-gecommitte git status/git diff (vooral als TASKS.md/
CLAUDE.md zelf ook wijzigingen tonen t.o.v. de laatste commit) —
dát is een aanwijzing dat een sessie iets heeft afgerond/uitgevoerd
maar de afsluitroutine (commit+push, zie hieronder) nog niet heeft
gedaan. Beide signalen samen (proces + ongecommitte diff) zijn het
sterkste bewijs van live werk — het script hierboven toont ze
gecombineerd. Zie zo'n situatie: bouw erop voort waar zinnig, maar
commit niet over andermans nog-niet-afgeronde wijziging heen zonder
navraag.Bevestigd goed werkende aanpak (2026-08-10 avond) voor het gezamenlijk
afwerken van TASKS.md: Bob voert alle builder-wijzigingen zelf uit in
zijn eigen browser (geen clipping-/coördinaatproblemen daar), Claude
geeft per taak de exacte stappen en verifieert elke taak na afloop
met een verse, losse export in plaats van op de builder-UI ("Synced",
geen foutmelding) te vertrouwen. In één avond leverde dit 9 afgeronde
taken op tegen een prettig tempo, en ving het minstens 2x een edit
die in de UI geslaagd leek maar bij export niet was doorgezet (zelfde
stille-niet-opgeslagen-patroon als elders in dit bestand, nu ook
bevestigd op een gewone Visibility-conditie, niet alleen bij de
bekende clipping-gevallen).
Vaste aanpak per taak:
TASKS.md, met taak-ID erbij (bv. "P1-3").Claude verifieert met een losse, snelle export (geen emulator/build nodig):
export PATH="/home/bob/fvm/bin:$HOME/.pub-cache/bin:$PATH"
flutterflow export-code --project uitgaanskrant-1qhvtd \
--dest /tmp/ff-check --token "$(cat ~/.ff_token)" \
--no-parent-folder --as-debug
gevolgd door een gerichte grep/Read op de betreffende regel(s).
Dit duurt seconden en is niet-destructief (los van de projectmap,
geen commit, geen emulator).
Klopt het: taak-ID hardop bevestigen, meteen de eerstvolgende taak geven (liefst al vooruit gecheckt terwijl Bob met de huidige bezig is — zie punt hieronder). Klopt het niet: exact melden wat er nog in de export staat, niet aannemen dat "Confirm geklikt" gelijk staat aan "opgeslagen".
Vooruit checken terwijl Bob bezig is: zodra Bob aan een taak begint, kan Claude alvast de eerstvolgende taak in de wachtrij pre-verifiëren (is hij nog steeds nodig, of intussen al door iemand anders/een eerdere sessie opgelost?) — scheelt een aparte heen-en-weer ronde.
Bob's expliciete volgorde-voorkeur (2026-08-11): eerst de volgende taak geven, dán pas de vorige verifiëren — niet andersom. Bob wil na het melden van "gedaan" meteen door kunnen bouwen i.p.v. te wachten tot Claude klaar is met verifiëren; de verificatie van de zojuist afgeronde stap mag daarna, gerust gecombineerd met de volgende voortgangsmelding.
Nooit stilzitten zolang er nog taken liggen (2026-08-14, expliciet bevestigd door Bob). Zodra Bob "gedaan" meldt: altijd meteen de eerstvolgende taak geven, ook voor kleine/optionele restpunten (bv. een laatste losse plek uit een audit) — niet eerst vragen "wil je deze ook nog meepakken, of laten we 'm liggen?". Kies zelf een default (meestal: gewoon meegeven) en ga door; alleen pauzeren met een echte ja/nee-vraag als de keuze de scope wezenlijk verandert (bv. een hele taak overslaan) én Bob niet al liet blijken dat hij liever gewoon doorwerkt.
Bob geeft na afronding van een cluster ook een eigen commit in FlutterFlow's interne versiebeheer ("main"/"Synced" bovenin) — los vangnet naast Claude's export-verificatie.
Altijd het taak-ID noemen bij elke gegeven stap en elke bevestiging (Bob's expliciete voorkeur) — voorkomt verwarring over welk punt van de lijst besproken wordt.
Standaardvraag om een nieuwe sessie mee te starten (plak dit als eerste bericht in een nieuwe chat om meteen door te pakken):
Leg mijn actielijst (
TASKS.md) voor me voor, één taak tegelijk — ik voer de builder-stappen zelf uit, jij verifieert daarna met een verse export voor je de volgende geeft. Noem altijd het taak-ID. Begin met [P0 eerst / een specifiek gebied, bv. "Login-pagina" / "verder waar we gebleven waren"].
Hoeveel taken per sessie — advies: geen harde limiet, maar een
sessie werkt het prettigst als hij één samenhangend cluster afmaakt
(zelfde FlutterFlow-pagina/component-groep, sluit aan bij de bestaande
"groepeer per paginagebied"-vuistregel hierboven) in plaats van
kriskras door de hele lijst te gaan — dat scheelt heen-en-weer-
navigeren én houdt de context overzichtelijk. Als praktisch richtgetal:
5-10 kleine, mechanische taken (zoals vanavond) is een goede,
vermoeidheid-arme klip; grotere/lastigere taken (P1-6's ~45 treffers,
P1-15's conditie-gepuzzel) tellen zwaarder en verdienen een eigen
sessie of een kleiner deelblok. Sluit elke sessie af met de
afsluitroutine hieronder (TASKS.md bijwerken + committen) — dan kan
een sessie ook prima na 2 taken stoppen zonder dat er iets verloren
gaat.
Aan het eind van elke sessie/taak:
TASKS.md: een afgeronde taak wordt volledig verwijderd
(niet gearchiveerd — onnodige context/kosten voor latere sessies).
Elke resterende open taak moet zelfstandig te begrijpen zijn
(concreet, met bestandspad) zonder de ontstaanschat gelezen te
hebben.CLAUDE.md: alleen aanvullen met blijvend herbruikbare inzichten
(conventie, architectuurkeuze, bekend valkuil-patroon) die een
latere sessie anders opnieuw zou moeten uitzoeken. Geen
sessieverslag/changelog. Ruim verouderde info op i.p.v. eronder te
plakken — dit bestand moet klein en scanbaar blijven..md-bestanden direct (geen
FlutterFlow-export nodig voor pure documentatiewijzigingen).Geen apart memory-systeem meer nodig voor projectfeiten — die
staan allemaal hier en in TASKS.md, wat al elke sessie automatisch
geladen wordt. (Auto-memory-bestanden zijn per 2026-08-04
opgeschoond omdat ze dit bestand 1-op-1 dupliceerden.)
Dit project wordt gebouwd via FlutterFlow (app.flutterflow.io). De FlutterFlow-cloudomgeving is de bron van waarheid, niet deze lokale code-export.
lib/custom_code/) — ook die voert Bob in via de Custom
Code-editor op de website, niet hier lokaal.flutter
analyze), maar voer het eindresultaat uit via de builder (zelf via
browser-automation, of als instructie aan Bob). Ga nooit uit van
behoud van een lokale bestandswijziging.git commit.mcp__claude-in-chrome__* (Bob's gedeelde Chrome), niet
de in-app Browser pane.navigate-aanroep naar
een FlutterFlow-URL 2x achter elkaar 5 minuten vast (op andere
MCP-calls in dezelfde browser, bv. tabs_context, geen probleem) —
vermoedelijke oorzaak: een vergrendeld/inactief scherm maakt de
extensie/CDP-brug onbetrouwbaar. Praktisch gevolg: builder-UI-werk
is geen geschikte taak voor een sessie terwijl Bob niet achter zijn
scherm zit (ook niet via /loop of een geplande taak) — Bash/
code-audit/emulator-werk (geen browserafhankelijkheid) juist wel.Projectdirectory = eigen schone git-repo, origin
ssh://gogs.digitalforce.tv:2222/Uitgaanskrant.com/flutterflow.git,
branch master. Los van de grotere/rommelige repo hoger in de
mappenstructuur (AndroidStudioProjects, Flutter-SDK e.d.) — commits en
pushes horen hier.
Vaste workflow na elke afgeronde taak (staand akkoord, geen aparte
bevestiging nodig): niet los flutterflow export-code, maar:
export PATH="/home/bob/fvm/bin:$HOME/.pub-cache/bin:$PATH" && /home/bob/Projects/ff-run-fvm.sh emulator-5554 uitgaanskrant-1qhvtd -s
export PATH=... prefix is verplicht — Bash draait
niet-interactief, ~/.bashrc wordt niet geladen. Zonder prefix
faalt het script stil ("fvm: command not found" — geen harde error).Standaard testen/QA-doorloop: --profile, niet debug (Bob's
besluit 2026-08-21). Aanleiding: na een schone herinstallatie
van beide fysieke testtoestellen voelde de app "enorm traag" aan —
bleek geen kapot toestel/instelling (Developer Options,
animatie-schalen, achtergrondbelasting allemaal normaal, bevestigd
via adb shell settings get/top), maar gewoon een debug-build
(dumpsys package com.uitgaanskrant.app toonde
flags=[ DEBUGGABLE ... ] op beide toestellen) — Flutter's
debug-mode is met opzet veel trager (JIT i.p.v. AOT, extra
assertions, hot-reload-machinery actief) en geeft dus geen reëel
beeld van hoe de app voor een eindgebruiker aanvoelt. Vaste regel
vanaf nu: gewoon rondkijken/een fix visueel verifiëren op een
device → los van ff-run-fvm.sh direct
fvm flutter run --profile -d <device_id>
(AOT-gecompileerd, dicht bij release-snelheid, DevTools/profiling
blijven bereikbaar, echte runtime-excepties/crashes blijven gewoon
zichtbaar — alleen hot-reload/-restart en debug-only asserts
vervallen). Volledige debug-mode (ff-run-fvm.sh, met de
ingebouwde live-'r'-export-plus-hot-reload-loop) blijft gereserveerd
voor het moment dat je daadwerkelijk een specifieke bug actief aan
het jagen bent (stack traces nodig, print-debugging, snel
itereren op een fix) — niet voor een gewone testronde. ff-run-fvm.sh
zelf heeft geen ingebouwde --profile-optie (start altijd kaal
fvm flutter run -d <device>, dus debug) — voor profile dus altijd
het directe fvm flutter run --profile-commando gebruiken, niet het
script.
Gradle-build faalt plots met Error resolving plugin [id:
'dev.flutter.flutter-plugin-loader' ...] > <versienummer> +
een "restricted method"-warning erboven: check eerst welke Java
Flutter gebruikt, niet de code. Bevestigd 2026-08-17: Android
Studio update zichzelf via snap automatisch, en de nieuwste
snap-revisie bundelt een steeds nieuwere JBR (ingebouwde Java) —
Flutter kiest bij het bouwen altijd "de JDK van de nieuwste Android
Studio-installatie" als eerste prioriteit. Gradle 8.12 (dit project)
kan niet overweg met Java 24+, en het versienummer achteraan die
foutmelding is precies de Java-versie van die nieuwe JBR (bv.
25.0.2) — een afgekapte/vervormde foutmelding, geen echte
plugin-versie-mismatch. Vaste fix, één keer nodig, overleeft een
export (globale Flutter-CLI-instelling, geen projectbestand):
fvm flutter config --jdk-dir=/usr/lib/jvm/java-17-openjdk-amd64
(systeem-Java 17 is stabiel, geen snap-auto-update-risico). Check bij
twijfel welke JDK's beschikbaar zijn met
ls /snap/android-studio/*/jbr/bin/java (elke revisie los te
aanroepen met -version) en readlink -f /snap/android-studio/current.
Interactief script (device-run + hot-restart-loop) → Bash met
run_in_background: true. Volg tot minimaal "All done!" én
idealiter een succesvolle app-launch (Launching lib/main.dart...,
geen nieuwe EXCEPTION CAUGHT BY RENDERING LIBRARY).
Testdevices (vanaf 2026-08-09): twee lokale AVD's, geen fysieke
toestellen meer. emulator-5554 (licht_avd, telefoon-formaat,
laag RAM-profiel) en emulator-5556 (tablet_avd, tablet-
schermverhouding) — beide starten automatisch bij Bob's
desktop-login via systemd user-services
(~/.config/systemd/user/android-emulator*.service). WayDroid en
losse fysieke devices zijn niet meer in gebruik (WayDroid's
adb shell screencap leed structureel aan PTY/CRLF-corruptie bij
screenshots — vandaar de overstap). Check altijd eerst met
adb devices welke van de twee daadwerkelijk draaien vóórdat je
ff-run-fvm.sh of de android-mcp-tools gebruikt — draait geen van
beide: navragen bij Bob i.p.v. blind op het eerste beschikbare
device te draaien. De android-mcp-server-tools
(mcp__android__*) accepteren een device_id-parameter per call,
dus beide devices zijn los aan te sturen zonder config-wijziging —
handig om ontwerp/layout op zowel telefoon- als tabletformaat te
verifiëren (bv. relevant voor P1-11's responsive-grid-taak).
Correctie 2026-08-17: "geen fysieke toestellen meer" is niet meer
hard waar — Bob gebruikt voor een live pair-fix soms ook zijn eigen
fysieke Xiaomi/MIUI-telefoon (adb-serial bv. K7V8DYTSMVTW6XBI,
ro.product.model=2409BRN2CY), vooral geschikt om een device met
echte on-screen 3-knops-navigatiebalk te testen (de twee AVD's hebben
dat niet). Check dus met adb devices wat er nu verbonden is i.p.v.
aan te nemen dat het altijd een van de twee AVD's is. Let op MIUI-
specifieke ANR's bij snelle geautomatiseerde adb-input: op dit
fysieke toestel triggerde een korte reeks losse adb shell input
text/input tap-calls achter elkaar (zonder pauze) herhaaldelijk een
"Uitgaanskrant.com isn't responding"-dialoog (MIUI's eigen
"MIUIScout"-mechanisme, zichtbaar in logcat als W MIUIScout ANR) —
dit is een MIUI/debug-build-artefact van de invoersnelheid, geen
crash van de app zelf. Bouw bij dit toestel bewust een korte
sleep/wachttijd tussen opeenvolgende input-acties in i.p.v. ze
direct na elkaar te vuren.
AVD crasht/valt soms zelf uit (los van de app) — diagnose via
journalctl --user -u android-emulator.service (telefoon) of
...-tablet.service (tablet), niet via adb logcat. Bevestigd
2026-08-21: emulator-5554 crashte zelfstandig tijdens een sessie
(systemctl --user status toonde code=dumped, signal=SEGV, met
een geheugenpiek naar 20GB vlak ervoor) — de service zelf (en dus
journalctl) overleeft zo'n crash en laat de stacktrace/reden zien;
het device verdwijnt gewoon uit adb devices zonder verdere uitleg.
systemctl --user show <service> -p NRestarts laat zien of
systemd 'm al automatisch herstart heeft (Restart=on-failure staat
aan, dus meestal komt-ie vanzelf terug op een active running na
10-30s). Valkuil bij het meten van "gebeurt dit nu live?" via
adb logcat: zonder eerst adb logcat -c (buffer legen) dumpt
een nieuwe adb logcat-aanroep eerst de hele bestaande ring
buffer in een korte burst (kan makkelijk 20.000+ regels/paar
seconden zijn, inclusief oude boot-logs) vóórdat 'm daadwerkelijke
live regels toont — een timeout 3s adb logcat | wc -l op een
ongeleegde buffer geeft dus een compleet misleidend "er gebeurt hier
van alles"-beeld. Altijd adb logcat -c vóór een korte live-meting.
Een EXCEPTION CAUGHT BY RENDERING LIBRARY/crash tijdens de run is
niet per se een regressie van je eigen wijziging — check eerst de
stack trace op bestandsnaam. lib/kanweg/ is Bob's eigen
scratch-testgebied (zie Opschonen hieronder) en kan legitiem crashen
zonder dat dat iets met de huidige taak te maken heeft; de app kan
daar staan door een eerdere hot-restart die Bob's laatst bezochte
route onthield, niet per se doordat het de echte initialLocation is
(check nav.dart).
Slechts één volledige EXCEPTION CAUGHT BY RENDERING LIBRARY-dump
per proceslevensduur, ongeacht hoeveel verschillende widgets
daarna overflowen. Flutter's dumpErrorToConsole toont de volledige
stack trace + widget/bestand/regel alleen bij de allereerste
exceptie sinds process- of hot-restart; elke volgende (ook een heel
andere widget/pagina) wordt ingekort tot Another exception was
thrown: <bericht> zonder bestandslocatie. Bevestigd 2026-08-09
(P1-13-onderzoek): de Home-pagina's al bekende overflow
(PUitgaanSliderKaartComponent, P1-3) consumeerde de enige volledige
dump nog vóórdat er genavigeerd kon worden naar de eigenlijk
onderzochte pagina — alle latere overflows op die andere pagina
bleven anoniem. Om een verse stack trace voor een specifieke,
latere crash te pakken: eerst een hot-restart (R) sturen vlak
vóór je die crash reproduceert, ván een schone/crash-vrije pagina uit
— dat reset de teller. Een run_in_background-Bash-aanroep van
ff-run-fvm.sh heeft stdin op /dev/null (niet-interactief),
dus r/R is dan niet te versturen. Kale mkfifo alléén werkt
niet (Flutter's keyboard-handler test op een echte TTY vóórdat hij
raw keypresses verwerkt — een FIFO is er geen, dus de R komt nooit
aan, ook al accepteert flutter run de pipe zonder foutmelding).
Bevestigd werkend recept (2026-08-09): setsid op zowel de
FIFO-writer als de flutter run-aanroep zelf, elk in een eigen
run_in_background-Bash-call:
mkfifo /pad/naar/fifo
setsid bash -c "exec 9>'/pad/naar/fifo'; sleep 3600" < /dev/null > /dev/null 2>&1 &
disown
# aparte tool-call:
setsid script -qc "fvm flutter run -d <device>" /pad/naar/logfile < /pad/naar/fifo > /dev/null 2>&1 &
disown
# later, weer een eigen call, om te restarten:
printf 'R' > /pad/naar/fifo
setsid geeft beide achtergrondprocessen een eigen sessie los van de
Bash-call die ze startte, dus ze overleven gewoon over meerdere losse
tool-calls heen. Flutter DevTools (de devtools-URL uit dezelfde
run-log) is zelf ook een Flutter-Web-canvas-app zonder toegankelijke
DOM — geen bruikbare omweg om alsnog bij de volledige historische
foutenlijst te komen.
Update 2026-08-09: het R+deep-link-race-probleem is opgelost —
gebruik --route bij het starten in plaats van een hot-restart +
losse deep-link. fvm flutter run -d <device> --route "/pad?param=waarde"
zet Flutter's defaultRouteName al vóór de eerste frame, dus
go_router opent direct die pagina — een tussenliggende Home-build
(en dus Home's eigen overflow, die anders de enige volledige-dump-
slot opsoupeert) vindt niet plaats. Geen FIFO/R/am start meer
nodig wanneer de doelpagina zelf met een directe route+query-param
bereikbaar is. Een hot-restart (R) blijft altijd terugvallen op de
initialLocation/Home, en een deep-link-am start vlak daarna wordt
door de net-herstarte app niet opnieuw verwerkt (adb toont dan
"Activity not started, intent has been delivered to currently
running top-most instance" terwijl de app toch op Home blijft) — dus
die combinatie blijft de dump-slot-race verliezen. Het FIFO/R-
recept hierboven blijft wel nodig voor pagina's die niet met een
kale --route te bereiken zijn (bv. een crash die pas na meerdere
gebruikersacties optreedt).
Daarna automatisch (of gebruik ff-commit.sh, zie hieronder):
git status --short, dan git add — niet blind -A. Alleen
echte FlutterFlow/app-wijzigingen (lib/, android/, ios/,
pubspec*, .gitignore). .claude/ blijft uitgesloten
(sessiestate, geen app-code). Bob's eigen concurrente wijzigingen
horen gewoon mee in dezelfde commit.git commit met duidelijke boodschap.git push (-u origin master als tracking nog niet staat).Valkuil: flutterflow export-code overschrijft .gitignore bij
elke export terug naar FlutterFlow's standaardversie. Voeg na elke
export, vóór staging, deze regel weer toe als hij ontbreekt:
# Claude Code session state (not app code)
.claude/
(CLAUDE.md zelf overleeft een export altijd — check voor de
zekerheid toch even.)
/home/bob/Projects/ff-commit.sh (2026-08-09) automatiseert bovenstaande
3 stappen + de .gitignore-valkuil-check in één keer:
/home/bob/Projects/ff-commit.sh "commit message" # app-code modus (lib/android/ios/pubspec*/.gitignore)
/home/bob/Projects/ff-commit.sh --docs "commit message" # alleen CLAUDE.md/TASKS.md
Herstelt automatisch de .claude/-gitignore-regel, staget alleen wat
echt gewijzigd is binnen de relevante paden, toont wat wél/niet
meegaat, en vraagt bevestiging vóór commit+push. Optioneel
--dir <pad> voor een ander projectpad dan de default
(ff-app/uitgaanskrant-1qhvtd).
Vóór een niet-triviale taak (bugfix, nieuw patroon, integratie): kort online zoeken naar bestaande oplossingen i.p.v. zelf trial-and-error in de builder. Geldt niet voor mechanische herhaling van een patroon dat al bevestigd werkt.
Een letterlijke tekst met 1 ingesloten variabele (bv. een JSON-body
{"entity_id": <nid>, "entity_type": "node"}) als waarde van een
Custom-Action-String-argument: geen concatenatie-optie in het
argumentenpaneel — bouw een kleine Custom Function. Bevestigd
2026-08-24/25 (P1-7 Restpunt A, horeca-hartjes Drupal-sync): het
"Value"-veld van een Custom Action-argument (bv. drupalRequest's
body) accepteert óf vrije typetekst (puur literal) óf een volledige
vervanging door 1 variabele via het icoontje naast "Value" (Set
Variable-dialoog) — nooit allebei tegelijk. De dialoog's "Source"-lijst
toont wel een optie "Combine Text", maar niet verder onderzocht
(bleek niet nodig zodra de Custom-Function-route werkte). Werkende
oplossing: een nieuwe Custom Function toevoegen (Custom Code-tab →
+ → Function), bv. String favorietenBodyNode(String nid) => '{"entity_id":
' + nid + ', "entity_type": "node"}'; — daarna is die functie gewoon
kiesbaar als waardebron in het Value-icoontje-dialoog (Source →
Custom Functions), met een eigen geneste Set-Variable-stap per
functie-argument om dat argument aan de juiste component-/page-
parameter te binden. Zelfde procedure ook bruikbaar voor andere
"body/URL met 1 variabele"-gevallen. Bijvangst: de geneste
Set-Variable-dialoog (voor het functie-argument) liep hier 2x tegen de
al bekende freeze aan (zie hieronder) — sleep 'm bij twijfel meteen
omhoog (drag op de titelbalk) vóórdat je gaat zoeken, dat voorkwam de
freeze in de 3e/4e poging elke keer.
Een pagina die nog geen Drawer/AppBar heeft alsnog toevoegen
(bv. een pagina zonder header/hamburger-menu): via slepen, niet via
rechtsklik. Gevonden 2026-08-19 (Favorieten-pagina miste een
header/drawer volledig). Stappen:
Drawer/AppBar-slot toe als eigen tree-node, sibling van
body.drawerComponent, HeaderButtonsComponent): rechtsklik/Insert
Widget op de nieuwe Drawer/AppBar-node werkt niet (geen
"Insert Widget"-optie in het contextmenu, en de node zelf toont geen
"+"-icoontje in de tree). Werkende route: open het linker
widget-paneel (Build-tab, 1e icoon) → 2e icoon in dat paneel se
lecteert "Project Components" → zoek de component op naam →
sleep de kaart direct naar de juiste canvas-dropzone (bij een
AppBar toont de canvas tijdens het slepen zelf de "Leading"/
"Title"/"Actions"-zones; bij een Drawer volstaat een sleep ergens
op het lege-Drawer-canvas). Voor een kale generieke widget (bv.
"Text") werkt exact dezelfde sleep-techniek vanuit het gewone
Elements-paneel (1e icoon in dat linkerpaneel).HeaderButtonsComponent's
showBackButton) krijgt na het slepen gewoon een normaal
Component Parameters-blok in het rechterpaneel — zet die zoals
altijd (zie ook P1-20's showBackButton-precedent verderop in dit
bestand: false voor een drawer-nav-bestemming zoals Home, niet
voor een subpagina).AppBar een
HeaderButtonsComponent als title krijgt (die zelf al een eigen
Scaffold.of(context).openDrawer()-knop bevat) én er inmiddels ook
een Drawer op de Scaffold staat, zet FlutterFlow automatisch
automaticallyImplyLeading: true — dat genereert een 2e,
overbodige hamburger-knop naast de component's eigen knop. Fix:
AppBar-node selecteren → Visibility-sectie → "Show Default
Button" uit (genereert automaticallyImplyLeading: false, exact
het patroon dat de bestaande pagina's al gebruiken).TabBar met veel/lange tabs: geen
losse widget-wrap nodig — de TabBar-node zelf heeft een
"Tab Bar Scrollable"-toggle (Search properties → "scroll").
Zet aan: labels tonen voortaan volledig uitgeschreven en de balk
scrollt horizontaal i.p.v. tekst af te kappen.Widget Tree-nesting nooit uit een codefragment afleiden — altijd de
tree/screenshot als bron van waarheid gebruiken. Bevestigde misser
(2026-08-11, HorecagelegenheidoverzichtKaartWidget): op basis van een
gedeeltelijke Read (alleen regels 280-343) leek de hartje-
AlignedTooltip genest in de Stack (samen met de afbeelding/
categorie-tags), puur op basis van de zichtbare inspringing in dat
fragment. Bob's eigen widget-tree-screenshot liet zien dat Tooltip
in werkelijkheid een directe sibling van Stack is (beide
rechtstreeks kind van Row), niet genest erin — de inspringing in een
afgekapt codefragment is geen betrouwbare graadmeter voor nesting
zonder de volledige haakjes-balans te checken. Vuistregel: geef
builder-pad-instructies pas na het zien van een tree-screenshot (of een
volledige, haakjes-sluitende Read van het component), niet op basis
van een korte code-grep/fragment-inferentie — bij twijfel eerst om een
screenshot vragen i.p.v. te gokken.
Conditioneel tonen/verbergen: gebruik de ingebouwde "Visibility"-sectie op het widget's eigen properties-paneel, niet Wrap Widget. Elk widget heeft rechtsboven in zijn eigen properties-paneel een "Visibility"- sectie (Conditional-toggle, Responsive per-device zichtbaarheid, Opacity-slider) — toggle "Conditional" aan om een conditie te zetten, geen wrap nodig. Bevestigd 2026-08-13. Correctie: de "Wrap Widget"-grid zelf bevat inderdaad géén "Visibility"-optie (volledige grid: Container, Card, Column, Row, Stack, ListView, GridView, Wrap, Form Validation, Blur, MouseRegion, Transform, Tooltip, ConditionalBuilder, Draggable, DragTarget, Flex, ShaderWrapper, AspectRatio) — dat klopt nog steeds, maar is de verkeerde plek om te zoeken. Wrap Widget → ConditionalBuilder blijft wél nodig wanneer je een widget conditioneel wilt vervangen door iets anders (een echte If/Then/Else met verschillende content per tak) — de losse Visibility-sectie hierboven is puur aan/uit, geen alternatieve inhoud.
**Een widget Expanded/Flexible maken: "Expanded" staat niet in de
Wrap Widget-grid (zie de volledige lijst hierboven) — gebruik
Wrap Widget → Flex, en zet daarna op die nieuwe Flex-node de
sectie "Expansion" (bovenaan zijn properties-paneel, vóór
"Padding" — 3 icoontjes, geen tekstveld/zoekbaar via "fit").
Bevestigd 2026-08-23 (Favorieten Tab 2, ListView zonder begrensde
hoogte gaf een RenderFlex-overflow): het laatste van de 3
Expansion-icoontjes bleek Expanded te zijn, gaf na Confirm
Expanded(child: Flex(direction: Axis.vertical, ...)) — exact het
gewenste effect (overflow weg). Volgorde van de 3 icoontjes
(None/Expanded/Flexible) niet met zekerheid getest voor andere
widget-types; bij twijfel gewoon proberen en met een verse export
verifiëren welk Dart-attribuut (Expanded(...) vs Flexible(...))
het echt genereerde — kost niets, geen destructieve actie.
ConditionalBuilder instellen na een Wrap Widget: klik op de
ConditionalBuilder-rij zelf, niet op If. Na "Wrap Widget →
ConditionalBuilder" selecteert de builder automatisch de If-tak
(teal gemarkeerd) zodat je meteen widgets in de THEN-kant kan zetten —
maar dat rechterpaneel toont dan de If-tak-eigenschappen, niet de
conditie zelf. Klik op de ConditionalBuilder-rij (één niveau
hoger in de tree) om de sectie "Conditions" → "Add Condition" →
"Single Condition" (First Value/Operator/Second Value) te krijgen.
"Set from Variable"-dialoog (Visibility → Conditional, of First/
Second Value binnen een Single Condition): de "Conditions" → "Single
Condition"-suboptie rendert vaak leeg/onklikbaar bij de eerste
expand-klik. Bevestigd structureel (2026-08-18, meerdere velden op
evenement_horecagelegenheid_widget.dart/horecagelegenheid_current_widget.dart):
na klikken op "Conditions" blijft de ruimte voor "Single Condition"/
"Combine Conditions" leeg (wel gereserveerde hoogte, geen zichtbare/
klikbare tekst) totdat je 2-4x extra op "Conditions" klikt — een
zoom-screenshot bevestigt dat de tekst er wél staat, alleen niet in
de normale (gecomprimeerde) screenshot-resolutie. Betrouwbaardere
route: typ in de zoekbalk bovenaan de dialoog (bv. "Single") vóórdat je
op "Conditions" klikt — de gefilterde lijst toont dan alleen
"Conditions" met exact 1 suboptie eronder, waardoor de klikpositie niet
meer geraden hoeft te worden. Zelfde aanpak werkt voor het latere
"UNSET"-veld binnen First/Second Value (klik toont soms alleen een
"+"/"-"-toggle zonder de eigenlijke UNSET-rij; gewoon nogmaals klikken
op dezelfde positie totdat de rij verschijnt, meestal 2-3x).
Checken of een List<String> App State-variabele een waarde bevat
("bevat dit item?"): geen aparte condition-operator, maar een
transform binnen "Set from Variable". Bevestigd 2026-08-11 op
favorieteHorecaNids (First Value van een Single Condition, Type:
Boolean): klik het Value-icoontje → Set from Variable → kies de
App State-lijst-variabele als bron → het "Available Options"-dropdownmenu
(zelfde UI-plek als de al bekende JSON-Path-optie, zie hieronder) biedt
dan lijst-transforms: List Contains Item, Filter List Items, Item at
Index, First Few Items, Is Set and Not Empty, Sort List Items, Unique
List Items, Number of Items. Kies "List Contains Item" → vul het
item-veld dat verschijnt met de te zoeken waarde (bv. een Component
Parameter zoals nid) → dit levert een Boolean terug. Terug in de
Single Condition: Operator "Equal to", Second Value = literal
true. Dit patroon komt vaker terug (favoriete-lijst-checks op
meerdere plekken) — niet opnieuw naar een "Contains"-operator in de
conditie zelf zoeken, die bestaat niet; ga altijd via deze
Set-from-Variable-transform-route op de lijst-variabele.
Rechterpaneel-clipping: gebruik de "Search properties..."-zoekbalk
i.p.v. scrollen. Het rechterpaneel (Widget Tree → property-editor)
scrollt via browser-automation vaak niet (muiswiel-events lijken geen
effect te hebben op die specifieke sectie) — de bekende
breedte-clipping (zie hieronder) geldt dus ook verticaal. Werkende
workaround: het zoekveld bovenaan het paneel ("Search properties...")
filtert op eigenschapsnaam (bv. "blur", "offset", "radius", "padding",
"shadow") en toont dan alléén die eigenschap, ongeacht scrollpositie —
vrijwel altijd binnen het zichtbare/klikbare gebied. Bij een
samengestelde eigenschap met 2 velden naast elkaar (bv. Offset X/Y)
kan het tweede veld nog steeds net buiten beeld vallen; een
Tab-naar-volgend-veld-workaround is niet betrouwbaar gebleken (de
waarde bleef soms op de oude staan zonder foutmelding, 2026-08-06
bevestigd op PUitgaanSliderKaartComponent's schaduw-offset). Voor
sliders zonder zichtbaar numeriek veld (bv. "Uniform Radius/Padding"
na het omzetten van per-hoek naar uniform): klikpositie op de track
geeft alleen een grove benadering, geen exacte waarde — prima voor
"ruwweg kleiner/groter", niet voor een precieze doelwaarde.
Ctrl+A + typen +
Return/Tab bleek meermaals niet vast te houden. Werkte
betrouwbaarder: triple-click op het veld → Delete → typen (i.p.v.
Ctrl+A → typen).https://app.flutterflow.io/code/<project>?component=<Naam> (of
?page=<Naam>) i.p.v. te klikken op het </>-icoon rechtsboven —
die iconenrij verschuift afhankelijk van app-state (een
commit-icoon verschijnt/verdwijnt), waardoor een vast coördinaat
soms een heel ander icoon raakt. Bevestigd 2026-08-06: 2x per
ongeluk een "Create Commit"-dialoog geopend, 1x een "Make Project
Public"-dialoog (met een Make Public-knop — nooit klikken), 1x de
app-preview. Geen van alle bevestigd/doorgeklikt, maar wel
tijdverlies — klik in die iconenrij rechtsboven nooit blind op een
onthouden coördinaat, altijd eerst een screenshot ná de vorige
actie. In de code-view zelf: gewoon muiswiel-scrollen werkt daar
evenmin betrouwbaar, gebruik Ctrl+F (opent een echte
browser-find-balk, werkt wel).
Ctrl+F opent hier een Monaco-stijl in-editor
zoekbalk (geen browser-native find), en die zoekbalk toonde bij
mij geen bruikbare matchteller (mogelijk zelf ook geclipt) — voor
het vinden van een specifieke regel werkte Ctrl+G ("Go to
Line") betrouwbaarder: typ een regelnummer (desnoods een schatting
op basis van de zichtbare structuur) + Enter, springt direct daarheen.
De editor is read-only (typen geeft een "Cannot edit"-tooltip) —
geen risico op per ongeluk wijzigen.Geneste "Set Variable"-dialoog lijkt vast te zitten. Bij een
conditie (ConditionalBuilder, Visibility → Conditional) op een
niet-triviaal type (JSON Path, API-response-veld, List<DataType>
function-argument) opent een tweede dialoog bovenop de eerste;
Confirm/Cancel reageren soms niet zichtbaar. Twee oorzaken, in
volgorde van proberen:
navigate naar dezelfde URL); wrap blijft staan, conditie moet
opnieuw.Component Name kan per ongeluk overschreven worden. Vlak na paginanavigatie kan een klik bedoeld voor "Search properties..." op het Component Name-veld landen (focus/z-order race), en typen hernoemt dan stilletjes het component. Zelfde risico bij een widget-tree zoekactie die per ongeluk double-click-to-rename triggert i.p.v. navigeren — druk direct Escape om te herstellen. Mitigatie: na elke click-before-type eerst een screenshot om focus te bevestigen, zeker vlak na navigatie. Herstel: rechtsklik component in zoekresultaten → "Rename Component".
Rechterpaneel kan te breed zijn voor de viewport. Sommige controls
(Expansion segmented control, Visibility → Conditional
expression-builder, maar ook simpele checkboxen zoals "Show Empty List
Widget" op een Carousel/ListView) renderen soms deels buiten beeld —
geen resize_window-probleem (niet zelf resizen, zie boven).
Bevestigd 2026-08-04: dit blijft optreden zelfs nadat Bob zijn eigen
venster al vergroot had — de FlutterFlow-app zelf lijkt de extra
breedte niet te gebruiken (real window 1970px, maar bruikbare
schermafbeelding/klikbare ruimte bleef begrensd tot ~1176px; de
JS-laag rapporteert wel de volle vensterbreedte, dus dit zit in hoe
Flutter Web rendert/schaalt, niet in het venster zelf). Geen
DOM/accessibility tree beschikbaar (canvas-rendering) — find en
read_page werken hier niet, alleen coördinaat-gebaseerd klikken.
Geprobeerd en zonder succes: direct klikken op meerdere
x-posities, klikken + Space-toets, horizontaal scrollen. Na 1-2
bevestigde pogingen stoppen en aan Bob vragen — geef het exacte pad
(component, tree-node, veldnaam) zodat hij het in seconden kan doen.
HomeUitgaanSliderComponent, PUitgaanSliderComponent,
EvenementComponent, horecagelegenheidCurrent, allemaal Carousel).
Niet meer opnieuw proberen per component — deze checkbox specifiek
is voor Claude via browser-automation niet bereikbaar, ongeacht welk
component. Verzamel in plaats daarvan de volledige lijst van
componenten die de fix nodig hebben en geef die in één keer aan Bob
(elk 10 sec in zijn eigen browser, geen viewport-beperking daar).Patroon: lege/ontbrekende afbeeldings-URL laat de app crashen.
CachedNetworkImage gooit een synchrone ArgumentError bij het
bouwen van de widget als imageUrl een lege string is — dit gebeurt
vóórdat er ooit een netwerkverzoek is, dus errorWidget/"Show Error
Image on Failure" vangt dit niet (dat vangt alleen échte
laadfouten zoals 404's). Werkende fix:
null als lege string (in-builder tooltip: "if the resulting
value is null or empty") — géén ConditionalBuilder nodig. Werkt hier
niet voor Image-widgets met Image Type: Network zolang er geen
gehoste fallback-afbeeldings-URL bestaat in dit project. Komt die er
ooit (bv. default-logo op FlutterFlow-CDN/Drupal-server): gebruik
dan Default Variable Value + "Show Error Image on Failure" — sneller
te bouwen dan ConditionalBuilder.Laad-placeholder voor CachedNetworkImage (trage/lege witte vlek
tijdens laden, zie TASKS.md P2-12): via de "Use Blur Hash"-toggle,
niet via een handmatige Shimmer/Container-wrap. FlutterFlow's
Image-widget heeft geen los "placeholder:"-veld in de rechterpaneel-UI
— de enige ingebouwde route is: Image-widget selecteren → Search
properties → "blur" → "Use Blur Hash" aanzetten → een
"Blur Hash String"-tekstveld verschijnt. Dit project heeft geen
per-afbeelding blurhash-data uit Drupal, dus een vaste, algemene
hash volstaat als neutrale grijze placeholder (geen per-foto
accurate blur, gewoon een nette laad-vlek):
L6PZfSi_.AyE_3t7t7R**0o#DgR4. Genereert automatisch
OctoImage(placeholderBuilder: (_) => Image(image:
BlurHashImage('...')), image: CachedNetworkImageProvider(...)) — geen
widget-tree-wijziging nodig. Valkuil: de eerste klik/typing in het
"Blur Hash String"-veld registreert soms niet (blijft leeg na Tab of
na een andere actie ertussen) — zelfde "ziet er opgeslagen uit, was
het niet"-patroon als elders in dit bestand. Altijd verifiëren met een
zoom-screenshot op het veld zelf (niet alleen het canvas) vóór je
verdergaat, en met een verse export dat de string niet leeg bleef.
"Is Set and Not Empty" is niet altijd beschikbaar als operator —
hangt af van hóe de First Value gebonden is, niet alleen van het
onderliggende veldtype. Bevestigd 2026-08-21 (Claude, ~45 min
vastgelopen op HomeUitgaantabelKaartComponent's $.logo-guard,
zie TASKS.md P1-24): een First Value gebonden via JSON Path op
een raw loop-item (bv. evenementenItem uit "Generate Dynamic
Children", geen gekoppeld Custom Data Type) krijgt in de builder-UI
het type-label "Json", en voor dat type biedt de operator-lijst
alleen Equal To/Not Equal To/Is Set/Is Not Set — "Is Set and
Not Empty" ontbreekt gewoon, ook al is de onderliggende waarde
gewoon een String. Uitgeprobeerd en allemaal doodgelopen: (1) een 2e
AND-conditie met != '' toevoegen — de "Second Value"-picker biedt
voor een Json-typed vergelijking geen vrij tekstveld, alleen een
variabele-bronkiezer (geen "literal"-optie, ook niet via de
zoekbalk-truc); (2) de First Value ombuigen naar "Predefined Path"
i.p.v. "JSON Path" in de hoop op een String-type — werkt alleen als
de bron een echt Custom Data Type heeft (raw JSON-loop-items hebben
dat per definitie niet, dus de path-kiezer toont dan gewoon niets);
(3) een "empty"/"length"-transform zoeken in de 2e "Available
Options"-dropdown (naar analogie van de bekende List Contains
Item-truc) — alleen "To Data Type" (custom models) en "No Further
Changes" beschikbaar. Vuistregel: "Is Set and Not Empty" werkt
betrouwbaar op een directe component-parameter die zelf al als
String gedeclareerd is (zie het bekende P0-8-recept — dat blijft
gewoon correct), maar niet op een JSON-Path-binding tegen een
raw/ongetypeerd loop-item. Bij zo'n loop-item-guard: of eerst het
component-parameter zelf String-typeren (als het als parameter
doorgegeven wordt aan een sub-component), of het probleem als "Is
Set" (null-check alleen) accepteren en de restkans op een lege-string-
.toString() → letterlijke "null"-tekst als cosmetisch, lager-
risico restpunt behandelen (P1-1's precedent) i.p.v. een crash.
Update 2026-08-21 (sessie 43): het ontbreken van een letterlijk
tekstveld voor "Second Value" hierboven is specifiek voor een
Json-typed First Value — voor een directe String/Image-Path-
component-parameter bestaat dat veld wél, alleen verstopt. Bevestigd
werkend op PUitgaanSliderKaartComponent's logo-parameter (Image
Path-type): Single Condition → First Value = de rauwe component-
parameter → operator "Not Equal To" → Second Value: klik niet op
het "+"/"—"-icoon (toggelt alleen open/dicht zonder invoerveld te
tonen), maar op de tekst-hint zelf (bv. "Is Set") — dat verandert de
rij naar een editable staat; klik daarna nogmaals op het "+"-icoon
ernaast → nu verschijnt een echt "Value"-tekstveld met placeholder
"Unset". Typ een willekeurig teken en verwijder het weer (bv. "x" +
Backspace) — dit forceert het veld om als letterlijke lege string
te registreren (placeholder-tekst springt om naar "[Empty String]"
i.p.v. terug te vallen op "Unset"/null). Resultaat:
if (widget!.logo != '') { ... } — exact de lege-string-guard die
eerder onbereikbaar leek. Geldt niet voor het Json-Path-geval
hierboven (HomeUitgaantabelKaartComponent's $.logo) — dat blijft
een echte FlutterFlow-beperking, zie TASKS.md P1-24.
Patroon: "Is Set and Not Empty"-conditie tegen een
Default-Value/valueOrDefault-gebonden veld is een no-op — en kan een
crash verbergen. Bevestigd 2026-08-07
(evenement_horecagelegenheid_widget.dart, zie TASKS.md P1-15):
gegenereerde code als if (valueOrDefault<String>(veld, 'placeholder')
!= null && valueOrDefault<String>(veld, 'placeholder') != '') is
altijd waar, omdat valueOrDefault bij een lege/ontbrekende bron
juist de placeholder-tekst teruggeeft (nooit null/''). Gevolg: een
widget die eigenlijk verborgen zou moeten zijn bij ontbrekende data
blijft zichtbaar mét de placeholder-tekst — en als die widget een
onTap/actie heeft die de (placeholder-)waarde gebruikt (bv.
launchURL(valueOrDefault(...)) op een social-media-/website-knop),
crasht de app bij tikken i.p.v. gewoon niets te doen. Fix: bind de
Visibility/If-conditie's First Value niet aan een veld waar al een
Default Value op zit, maar rechtstreeks aan de rauwe API-response-
expressie (dezelfde JSON Path als de widget's eigen Path-property, vóór
een eventuele Default Value-substitutie) met operator "Is Set" —
zelfde recept als de CachedNetworkImage-fix hierboven. Check dit
patroon bij elke knop/link die een API-veld gebruikt, niet alleen bij
de al bekende P1-6-lijst met lelijke placeholder-tekst — die twee
problemen (lelijke tekst vs. crash-bij-tikken) hebben dezelfde bron
maar zijn niet automatisch allebei opgelost met alleen een betere
Default Value-tekst.
Custom-Function met meerdere argumenten in een Set-Variable-actie:
tweede argument bevriest de pagina. Bij het configureren van een
multi-argument custom function (bv. gemeenteNaamById(lijst, id)) als
Set-Variable-waarde: het eerste argument instellen via de
"Search variables..." → categorie-expand → item-klik-flow werkt
betrouwbaar. Zodra je daarna probeert het tweede argument in te
stellen (klik op de argument-dropdown-selector, bv. van "lijst" naar
"id"), kan de hele pagina volledig bevriezen (alle clicks doen
niets meer, geen enkele visuele terugkoppeling) — bevestigd
reproduceerbaar 2026-08-05 op gemeenteNaamById, NIET opgetreden bij
hetzelfde patroon op provincieNaamById een moment eerder (niet
100% deterministisch, maar bij deze functie 3x achter elkaar
gereproduceerd). Reload lost dit niet volledig op zoals bij het
bekende "echt bevroren pagina"-patroon hierboven: de
Custom-Function-keuze zelf (bv. welke functie, welk App-State-veld het
target is) overleeft een reload wél, maar het al ingestelde eerste
argument (bv. lijst) gaat weer naar "UNSET" terug — dus geen
gratis doorstart, elke reload kost een herhaling van stap 1.
SelectStateDropDownComponent → DropDownGemeente → Actions →
On Selected → Action 1 → Set Fields → gemeenteSelectNaam): de
functie gemeenteNaamById staat al goed gekozen (herkenbaar aan
rode "gemeenteN…" tekst i.p.v. "Unset" in de Set Fields-lijst), nog
toe te voegen: argument lijst = App State gemeentelijst,
argument id = App State gemeenteSelectId, dan Confirm.EventCurrent's AppBar-Row, in totaal 5x
bevroren bij het klikken op "Confirm" (soms al bij de binnenste
Single-Condition-Confirm, soms pas bij de outer Confirm) ná het
instellen van een Single Condition (Is Set and Not Empty). Precies
dezelfde stappen lukten wél zonder freeze op HeaderButtonsComponent,
én lukten wél voor het vergelijkbare Gemeente-Custom-Function-
argumentenpunt na een computer-restart — dus dit is niet louter
algemene omgevingsinstabiliteit die met een restart oplost. Een
computer-restart tussen pogingen 2 en 3 loste dit specifieke geval
niet op (2x nieuwe freeze ná restart, identiek patroon). Blijft
onverklaard waarom dit specifieke widget/actie op dit specifieke
component structureel vaker vastloopt dan elders — mogelijk iets
aan de AppBar-Row-context van EventCurrent zelf. Na 5x: gestopt
met proberen, definitief overgedragen aan Bob (zie TASKS.md
P0-3). Vuistregel blijft: 1-2 pogingen (incl. 1 reload), dan
overdragen met exacte stappen — niet blijven proberen.Een nieuwe rij toevoegen aan een lijst met bestaande, vergelijkbare
rijen (bv. nog een ListTile in een menu): dupliceer een bestaande rij
i.p.v. Insert Widget te gebruiken. Rechtsklik de bestaande node →
Duplicate (Ctrl+D) → de kopie verschijnt als sibling → pas titel-
tekst en On Tap-actie aan op de kopie. Betrouwbaarder dan de bekende
Insert-Widget-onbetrouwbaarheid (zie hieronder), en scheelt het
handmatig opnieuw opbouwen van styling (padding, border radius,
tileColor etc.). Bevestigd 2026-08-24 op Favorieten' Gebruiker-tab
("Account verwijderen"-knop toegevoegd naast de bestaande
"Uitloggen"-knop).
"Launch URL"-actie staat onder categorie "Share" in de Actions-zoeklijst, niet onder "Navigation" — logisch te missen. Zoek in de Action Flow Editor gewoon op "Launch URL", dat vindt 'm ongeacht categorie.
Tooltip toevoegen aan een icon-only widget: rechtsklik → Wrap
Widget (Ctrl+B) → 4e rij van de grid (kan geclipt lijken) → 1e icoon
(spraakwolkje) = "Tooltip". Genereert een AlignedTooltip, geen losse
styling nodig. Werkt niet op iconen embedded als suffixIcon van een
TextFormField (bv. Login-pagina wis-/toon-wachtwoord-iconen — geen
losse wrapbare tree-node).
Een Row/Column met gegenereerde kinderen (List.generate(...))
van widget-type wisselen (bv. Row → Wrap tegen tag-overflow): gebruik
rechtsklik → "Replace", NOOIT "Wrap Widget" gevolgd door "Remove
Widget" op de oude node. Bevestigd 2026-08-12 (live pair-sessie,
HomeUitgaantabelKaartComponent): de "herhaal-over-lijst"-configuratie
(List.generate, incl. de parameter-binding zoals categorieTag:
categorieitemItem) hangt als eigenschap ván de omringende Row/Column
zelf, niet van het kind-template. "Wrap Widget" wrapt de Row in een
nieuwe Wrap (dubbele nesting, lost niets op) en de daaropvolgende
"Remove Widget" op de overbodige oude Row verwijdert de hele
lijst-generatie mee — resultaat is een enkel statisch,
ongebonden kind-widget i.p.v. een item per lijst-element (functionele
regressie, geen crash/foutmelding die het verraadt). Herstel: Ctrl+Z
(werkte binnen dezelfde tab/sessie, geen garantie na page-reload).
Bevestigd werkende, veilige methode: rechtsklik direct op de Row
→ Replace → kies Wrap. Dit wisselt alleen het widget-type,
List.generate + alle parameter-bindingen blijven intact (bevestigd
via verse export op meerdere componenten). Zelfde controlestap als
altijd: ná de wijziging verifiëren dat List.generate(...) nog in de
export staat, niet alleen dat de tree er goed uitziet.
Widget toevoegen: gebruik de kleine inline "Insert Widget", niet de
grote centrale "Insert"-modal. Rechtsklik op een Widget Tree-rij →
"Insert Widget" (of het kleine "+"-icoon naast een tree-node) opent
een compacte dropdown die betrouwbaar werkt: klik op een widget-kaart
voegt 'm meteen toe. De grote centrale Insert-modal (bv. via de
"+"-knop bovenaan het linkerpaneel) opent wél, en widget-kaarten lijken
klikbaar/highlighten bij hover, maar een klik registreert niet —
geen insertie, dialoog blijft open, geen foutmelding. Bevestigd
2026-08-05 op EventCurrent (Button toevoegen na
EvenementHorecagelegenheid): pas de inline-variant via rechtsklik
werkte. Sluit aan bij een eerdere observatie (2026-08-04, op
HorecagelegenheidCurrent): een widget toevoegen in een AppBar-Row
faalde herhaaldelijk stil via beide insert-varianten, terwijl exact
dezelfde inline-picker op een gewone body-Column wél meteen werkte —
mogelijk is een AppBar-Row specifiek een moeilijkere insertie-plek,
sowieso eerst de inline-variant op een Column proberen vóór de grote
modal.
HorecagelegenhedenOverzicht (een gewone Column, TabBar-node)
opende rechtsklik → "Insert Widget" herhaaldelijk wél de grote,
kapotte modal (klikken/dubbelklikken/zoeken op widgetnaam, niets
registreerde — 5+ pogingen). Blijkbaar niet 100% voorspelbaar per
context. Werkende omweg wanneer je alleen decoratie/styling nodig
hebt (geen echt nieuw kind-widget), bevestigd op ditzelfde
component: i.p.v. een nieuwe widget te inserten, de bestaande
node (bv. TabBar, of een Column met tags) Wrap Widget (Ctrl+B)
→ Container — dat dialoogvenster werkt wel betrouwbaar (bevestigd
meerdere keren deze sessie) — en geef die nieuwe Container dan een
BoxShadow/Fill Color/Border Radius via de
eigenschappen-zoekbalk. Voor een schaduw onder een widget (bv.
TabBar-afscheiding): Box Shadow → Shadow Color (hex-veld, bv.
33000000) → apart zoeken op "offset y" (aparte property, niet
samen met "offset" x/y in één filter) en "blur". Voor een simpele
achtergrond-scrim (bv. tegen storende achtergrond-afbeelding): alleen
Fill Color op de wrap-Container volstaat al, ook zonder eigen
padding als de wrap toevallig al een bestaande Padding-widget omvat.
Bij een écht nieuw structureel widget (geen bestaande node om te
wrappen) blijft de Insert-modal het enige pad — dan na 1-2 pogingen
stoppen en aan Bob overdragen, zoals altijd.Een tab toevoegen aan een bestaande TabBar: via de "Active Tab"-
dropdown, niet via Insert Widget. Gevonden 2026-08-15 (P1-7,
Favorieten kreeg een 4e tab "Gebruiker"). Selecteer de TabBar-node
zelf (niet een losse Tab) → rechterpaneel toont bovenaan "Active
Tab" (een dropdown die de huidige preview-tab kiest, met de
bestaande tabnamen als opties) → klik erop → onderaan de openklappende
lijst staat "+ Add Tab". Dit voegt in één klik zowel een nieuwe
Tab-label (in de TabBar's tabs:-lijst) als een bijpassende lege
TabBarView-pagina toe (in de tree als een nieuwe Tab+TabBar Page-
paar onderaan) — geen losse Insert Widget-stappen nodig, en geen
TabController-length-gedoe (FlutterFlow past die automatisch aan).
Nieuwe tab heet standaard "Tab N"; hernoem via de Tab-node selecteren
→ rechterpaneel "Label → Text"-veld. Let op: het rechterpaneel
kan na het typen even de oude waarde blijven tonen (react-render-lag)
— het canvas zelf is al bijgewerkt en is de betrouwbaardere check; een
her-select van de node bevestigt de opgeslagen waarde.
Tab-nodes te herkennen aan een "+"-icoon
(het icoon-lettertype voor het Tab-widgettype), niet te verwarren
met een "voeg widget toe"-knop — het is gewoon de bestaande
tab-label-node, aanklikbaar als elke andere tree-rij.Per-tab "On Tap"-logica van een TabBar (bv. data ophalen bij het
wisselen van tab) leeft op de individuele Tab-node, niet op de
TabBar zelf. Gevonden 2026-08-17: de TabBar-node heeft zelf ook
een Actions-tab met een "Action Flow Editor", maar díe biedt alleen
generieke gestures (On Double Tap/On Long Press/On Tab Change) — leeg
tenzij je het zelf instelt. De bestaande, al werkende per-tab-fetch
zat in werkelijkheid op elke losse Tab-node (bv. TabPersAgenda) se
lecteren → Actions-tab → "On Tap: N actions". FlutterFlow bundelt deze
losse per-tab-acties in de export tot één onTap: (i) async { [...][i]() }
op de gegenereerde TabBar. Praktisch gevolg: zo'n per-tab-actie
vuurt nooit voor de al-actieve standaardtab bij page-load (onTap
reageert alleen op een echte tik) — heb je ook data nodig zodra de
pagina opent, voeg dan apart een "On Page Load"-trigger toe op de
Scaffold (zelfde als bij een gewone pagina) met dezelfde actieketen.
drupalRequest's resultaat), plakt FlutterFlow die
variabele met exact dezelfde naam als een nieuw, apart
model-veld — dat geeft in de export een echte Dart-compile-error
("X is already declared in this scope", 2x gedeclareerd in
..._model.dart). Build faalt dan pas bij de eerstvolgende
flutter run/export-code, niet zichtbaar in de builder-UI zelf
(geen foutmelding daar). Fix meteen ná het plakken: open de
geplakte Custom-Action-node → scroll (of klap "method"/eerste
argument dicht om ruimte te maken, zie de rechterpaneel-clipping-
notitie hieronder) naar "Action Output Variable Name" → geef 'm
een unieke naam → ga naar de vólgende actie in dezelfde geplakte
keten die deze variabele gebruikt (bv. een "Update Page State") →
klik het Value-veld → potlood-icoon → Source "Action Outputs" →
kies de net-hernoemde variabele opnieuw (de oude referentie blijft
anders naar de verwijderde/foutieve naam wijzen). Altijd
verifiëren met een verse export + flutter analyze vóór je
opnieuw deployt — dit patroon is 2x in dezelfde sessie misgegaan
voordat de fix duidelijk was.Meerdere acties na elkaar op één trigger (bv. eerst App State opruimen, dán navigeren) instellen via de "Action Flow Editor", niet door in het compacte Actions-paneel te blijven scrollen. Na de eerste actie (Actions-tab → Add Action → ...) toont het paneel geen zichtbare "volgende actie toevoegen"-knop meer onderaan de eerste actie's eigen instellingen. Klik in plaats daarvan op "Edit" naast "Action Flow Editor" (bovenaan de Actions-tab) — dat opent een los canvas met de acties als verbonden blokken en een "+"-connector tussen/onder elk blok. Klik de "+" onder het laatste blok → "Add Action" (naast Add Conditional/Loop/Parallel/Paste) → kies de volgende actie zoals gewoonlijk. "Close" rechtsboven sluit het canvas weer, de acties blijven gewoon staan.
userMail — gewoon de
veldnaam typen als hij niet meteen in de eerste ~6 resultaten
staat) → per toegevoegd veld een eigen "Select Update Type" instellen
op "Clear Value" — reset het veld naar zijn lege/default-waarde
(''/0), genereert FFAppState().deleteX(); FFAppState().x = ''/0;
in de export. Voor "Navigate To" ná zo'n reset: zet "Allow Back
Navigation" uit als de gebruiker niet terug moet kunnen naar de
net-verlaten (nu uitgelogde) pagina — genereert context.goNamed(...)
i.p.v. context.pushNamed(...), dus geen back-stack-entry.Set-Variable/parameter-waarde koppelen aan App State: klein
icoontje naast het "Value"-label, niet de tekstinvoer zelf. Bij een
actie-parameter (bv. Navigate To met page-parameters) toont het
"Value"-veld standaard een tekstinvoer (voor een letterlijke waarde).
Om aan een variabele (App State, Page Parameter, …) te binden: klik op
het kleine icoontje vlak vóór/naast het woord "Value" (niet in het
invoerveld zelf) — dat opent een "Set Variable"-dialoog met een
zoekbalk en een "Source"-lijst (Page Parameters/App State/Global
Properties/…) om uit te klappen en te doorzoeken. Dit icoontje is
makkelijk te missen/verkeerd te klikken (kleiner dan de rest van het
paneel) — bij twijfel zoom gebruiken op de regio rond "Value" om de
exacte pixelpositie te bepalen vóór je klikt.
itemBuilder-variabele als "evenementen item"), 2026-08-07
bevestigd: het item zelf selecteren in de Set Variable-dialoog is
niet genoeg — je krijgt dan het hele JSON-object, niet een veld
ervan. Klik het item, dan "Available Options" (dropdown, staat
eerst op "No Further Changes") → "JSON Path" → typ het pad met een
voorloop-$, bv. $.nid of $.horecagelegenheidid (exacte
veldnamen uit de API-respons, zie lib/backend/api_requests/api_calls.dart).
Werkt identiek aan de al bestaande getJsonField(item, r'''$.veld''')-code
elders in dit project — dit is precies hetzelfde patroon, alleen via
de builder-UI ingesteld i.p.v. handmatig in code.serializeParam('', ParamType.String) — een lege string i.p.v. de
parameter-referentie. Reproduceerbaar herstel: Value-icoontje
opnieuw aanklikken, dialoog opnieuw openen, dezelfde parameter
opnieuw selecteren — de tweede keer sloeg het wél correct op. Vertrouw
dus nooit alleen op het rechterpaneel na zo'n binding; controleer
altijd even via View Code (zie hieronder) dat de gegenereerde
code echt naar de variabele verwijst en niet naar een lege/letterlijke
waarde.Zelfde "leek opgeslagen, was het niet"-patroon ook bevestigd op een
kaal letterlijk Action Flow Editor-tekstveld (niet alleen bij
variabele-bindingen). Bevestigd 2026-08-17 (login_widget.dart,
"Inloggen"-knop → Action Flow Editor → "Show Snack Bar"-actie →
"Snack Bar Message" → Value): eerste keer typen + wegklikken toonde de
tekst gewoon in het paneel, maar een verse export gaf toch
content: Text('', ...) — lege string. Tweede poging (triple-click op
het veld → opnieuw typen → Tab om te bevestigen → her-selecteren van
de actie ter controle dat de waarde bleef staan) sloeg wél correct op,
bevestigd via nieuwe export. Vuistregel dus verbreed: bij élk
Action-tekstveld (niet alleen Component-Parameter-bindingen) een
losse verificatie-export doen vóór je verdergaat — het rechterpaneel
alleen is niet genoeg bewijs dat een waarde echt is doorgezet.
API Calls-paneel: "Delete" op een losse call kan ook niet doorzetten
naar de export — niet alleen een widget-tree/canvas-probleem.
Bevestigd 2026-08-14 (FavorietenAgendaTESTKANWEGCall, P2-7): Delete-
knop → bevestigingsdialoog ("Delete this API Call from the project?")
→ Delete — de dialoog sluit zichtbaar netjes (geen freeze), maar de
call staat na een page-reload gewoon weer terug in de lijst, 2x
gereproduceerd, ook bevestigd via een losse verse
flutterflow export-code (klasse nog gewoon aanwezig). Dit paneel is
een platte lijst, geen canvas-coördinaten — dus dit is geen instantie
van de bekende widget-tree-klikonbetrouwbaarheid, eerder een breder
"ziet er opgeslagen uit in de UI, bereikt de export niet"-patroon (zie
ook P1-13/P0-3 punt 1 in TASKS.md) dat blijkbaar ook dit paneel kan
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).
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
testwaarde (bv. Cookie-header) kan per ongeluk blijven staan i.p.v.
[sessionname]=[sessionid]. Check ook het per-variabele
"Include"-vinkje in Response & Test — staat die uit, dan wordt de
letterlijke [varname]-tekst meegestuurd i.p.v. de testwaarde.
Custom Code-editor (Custom Functions/Widgets/Actions): tekst-editor,
betrouwbaar te bewerken — maar wijziging verschijnt pas in een export
ná een expliciete Ctrl+S. In tegenstelling tot widget-canvas/
rechterpaneel (coördinaat-onbetrouwbaar, zie hieronder) is dit een
echte Monaco-editor: getypte code (incl. auto-closing brackets/quotes)
komt gewoon correct aan, geen speciale precautions nodig. Kritieke
valkuil, 2026-08-09 bevestigd: de header toont "Synced" al direct
na typen, maar dat weerspiegelt niet dat de wijziging al naar de
export-pipeline is doorgezet — een flutterflow export-code direct
daarna (ook na een volledige herstart van ff-run-fvm.sh, dus geen
lokale cache-issue) gaf 2x achter elkaar nog de oude bestandsinhoud
terug. Pas ná een expliciete Ctrl+S in de editor (zichtbaar:
kort "Syncing…" i.p.v. "Synced", + auto-format herschrijft de
zojuist getypte regels) nam een verse export de wijziging over.
Vuistregel: na elke Custom Code-wijziging altijd Ctrl+S drukken
vóórdat je ff-run-fvm.sh/export-code draait, en bij twijfel eerst
verifiëren met een losse
flutterflow export-code --project <id> --dest /tmp/check --token
"$(cat ~/.ff_token)" --no-parent-folder --as-debug + grep op de
verwachte regel, i.p.v. blind op de "Synced"-badge te vertrouwen.
⌘+K-commandpalet (zoek op de custom-action-naam,
bv. "drupalLogin") — betrouwbaarder dan door de linkerzijbalk
klikken (die leed in deze sessie aan hetzelfde klik-coördinaat-
probleem als de widget-tree, zie hieronder). Let op: klik het
zoekresultaat aan met de muis, druk geen Enter — een nog openstaand
rechtsklik-contextmenu elders op de pagina kan de Enter-toets
onderscheppen (2026-08-09 1x per ongeluk een widget gedupliceerd op
deze manier; met Ctrl+Z + Delete op de duplicaat hersteld, geen
blijvende schade). Sluit dus eerst met Escape elk open contextmenu
vóór je ⌘+K gebruikt.Widget Tree/canvas: klik-coördinaten zijn NIET betrouwbaar 1-op-1
met wat een screenshot toont, en dit is geen vast te rekenen offset.
Los van de al bekende rechterpaneel-breedte-clipping (hierboven) bleek
2026-08-09: een klik op een zichtbare tree-rij selecteerde herhaaldelijk
een héél andere rij (soms 2, soms 3 rijen verderop, niet consistent
in pixels of in aantal rijen). Werkende aanpak: na een klik nooit
blind vertrouwen — verifieer met Home + Shift+End (selecteert de
hele regel/rij zonder iets te wijzigen) of een read-only zoom op het
rechterpaneel, en pas de klik-y-coördinaat handmatig aan op basis van
wat je net echt selecteerde, vóór je verdergaat. Reload van de
pagina (navigate naar dezelfde URL) hielp de eerstvolgende klik weer
correct te laten landen, maar het patroon kwam later opnieuw terug —
geen structurele fix gevonden, alleen de per-klik-verificatie hierboven.
double_click op een widget (zowel canvas als tree) selecteerde
herhaaldelijk de root-pagina i.p.v. in te zoomen — vermijd double-click
voor selectie, gebruik single-click + verificatie.
Delete-toetsaanslag wordt
door de Bash/computer-permissie-classifier geblokkeerd als
destructieve actie — niet bruikbaar, gebruik het menu-item.key-events, zelfs opzettelijk buiten-viewport-coördinaten (in de
aanname dat click-ruimte niet 1-op-1 met screenshot-ruimte is) —
niets kwam aan (waarde bleef letterlijk ongewijzigd). Dit is erger
dan de bekende checkbox-clipping: niet alleen "net buiten beeld",
maar functioneel onbereikbaar. Bob's tip (2026-08-09): voor een
Text-widget zit naast het "Text"-label een klein bolletje/
globe-icoontje dat de vertaling direct opent — sneller dan via
App Settings → Languages, en waarschijnlijk ook betrouwbaarder
bereikbaar in Bob's eigen (niet-geclipte) browser. Nog niet
bevestigd of dat icoontje voor Claude wél bereikbaar is (waarschijnlijk
ook geclipt, niet meer getest deze sessie) — bij een volgende
soortgelijke taak eerst dat proberen vóór je het opgeeft.HorecagelegenheidCurrent
(Text-adres, zie TASKS.md P0-8): 7+ pogingen (directe klik op de
toggle-track op meerdere x/y-posities, twee keer na elkaar toggelen,
left_click_drag over de track) gaven geen zichtbare
statusverandering, en meerdere keren sprong de widget-selectie
terug naar de pagina-root (rechterpaneel toonde dan opeens
Scaffold/Edit Drawer-properties). De omweg via rechtsklik → Wrap
Widget (Ctrl+B) opende het contextmenu zelf wél zichtbaar correct,
maar de vervolgklik op een menu-item viel alsnog ten prooi aan de
bekende tree-rij-offset-instabiliteit (offset was dit keer niet
constant: +79px op het ene moment, +64px een moment later — dus ook
de kalibratietruc hielp hier niet). Vuistregel: bij een taak die
puur de Visibility-Conditional-toggle nodig heeft (aan/uit, geen
vervang-logica), niet blijven proberen na 1-2 pogingen — teruggeven
aan Bob met de exacte veldnamen/JSON-paths, dat kost hem seconden per
veld.adb shell cmd locale set-app-locales <package> --locales nl-NL
(per-app override, Android 13+/API 33+) i.p.v. het systeembrede
settings put system system_locales + am broadcast -a
android.intent.action.LOCALE_CHANGED — dat laatste faalt zonder
root (Neither user ... nor current process has
android.permission.CHANGE_CONFIGURATION) en werkte zelfs als root
niet betrouwbaar zonder herstart. De per-app cmd locale-route werkte
in één keer, direct zichtbaar na een am force-stop + relaunch, geen
reboot nodig — dit is de snellere/betrouwbaardere manier om
P1-17-achtige locale-afhankelijke bugs te reproduceren/verifiëren.Actions-paneel: bestaat wél, gevonden op 2026-08-14 (ListTile,
niet op een Button geprobeerd, vermoedelijk hetzelfde) — eerdere
notitie hieronder ("niet gevonden ondanks uitgebreid zoeken") was
onvolledig zoeken, niet een echte afwezigheid. Selecteer het widget
→ kijk naar de kleine iconenrij direct onder de widgetnaam bovenin
het rechterpaneel (naast de paint-brush/Styling-icoon): het 2e
icoontje (een vertakkings-/node-icoon) opent de Actions-tab
(kop "Define Action", een "On Tap"-trigger-dropdown + "+ Add Action").
Daar: Add Action → Navigation → Navigate To → doelpagina kiezen →
Parameters → klik expliciet op "Pass" (niet op "+ Define" — dat
opent per ongeluk de doelpagina's eigen parameterschema-editor,
geen waarde-invoer voor déze actie) → per parameter verschijnt dan een
rij met een Value-veld → normale Set from Variable-flow (zie
elders in dit bestand) om de waarde te binden. Deze hele
Actions-tab was het ontbrekende stuk voor P1-9/P1-15's knop-condities
— bij een volgende poging op die taken eerst hier proberen i.p.v. de
Insert-Widget/Wrap-Widget-routes.
Generate Dynamic Children — hoe je een ListView/GridView/
StaggeredView een rij per API-response-item laat tonen (gevonden
2026-08-14, bevestigd werkend via P1-7 Tab 3). Dit is niet
hetzelfde als de "Backend Query" database-icoon-optie (die haalt alleen
de data op via een FutureBuilder, genereert geen herhaalde
kinderen) — het is een apart, makkelijk over het hoofd te ziene 4e
icoontje in dezelfde kleine iconenrij als hierboven (paint-brush,
Actions-node, database, dít 4e icoontje = sliders/kolommen-symbool,
play, save-as-template) — hover toont tooltip "Generate Dynamic
Children" ter bevestiging. Volledige flow:
ListTile, of een
Row/Container naar smaak) via Insert Widget.$.veldnaam, niet $[:].veldnaam zoals de
al bestaande establishmentX-getters in api_calls.dart gebruiken
— die zijn geschreven voor de hele array, niet voor een los item).ListView.builder(itemCount: ..., itemBuilder: (context, index) {
final xItem = xList[index]; ...})-structuur, identiek aan het
patroon dat al elders in dit project draait
(MasonryGridView.builder/StaggeredView op
HorecagelegenhedenOverzicht)."+ Add App State Variable"/"+ Add App Constant"-knop reageert niet
op Claude's klikken (2026-08-09). Op zowel ?tab=appValues&appValuesTab=state
als ...=constant (bereikbaar via ⌘+K → typ een bestaande App
State-naam → Enter, betrouwbaarder dan de linker-navigatierail
klikken — zie hieronder): een klik op de knop opent geen dialoog,
voegt geen rij toe (geverifieerd via de paneel-eigen zoekbalk erna
— 0 resultaten), en blind doortypen na de klik komt nergens terecht.
6+ pogingen (directe klik, exacte pixel-herpositionering op zowel
tekst als "+"-icoon, klik+wachten 2s, klik+blind-typen) — geen
resultaat, geen zichtbare foutmelding. Navigeren tussen "App State"/
"Constants" in de linkerrail en het zoekveld typen werkten in dezelfde
sessie wél probleemloos, dus dit is geen algemene klik-doodheid van de
pagina, specifiek deze twee "Add"-knoppen. Los probleem: mogelijk
gerelateerd aan de bekende Confirm-freeze-patronen elders in dit
bestand, maar hier verscheen zelfs geen gedeeltelijke dialoog om
vast te lopen — niets gebeurde. Ook: de rij-✏️-edit-iconen op
bestaande App State-velden reageerden even onbewogen. Nog niet
geprobeerd: browser-zoom (blijkt via keyboard-shortcut geblokkeerd,
"page zoom keyboard shortcuts are not supported" — alleen de
zoom-tool voor screenshots, niet de pagina zelf); Bob's eigen
browser (geen bekende clipping daar). Vuistregel voor volgende
sessie: nieuwe App State-velden/Constants zijn voor Claude niet
zelfstandig toe te voegen — na 1-2 pogingen overdragen aan Bob met
exacte veldnaam+type-specificatie (kost hem seconden per veld).
Drupal 7 Services-module: een nieuwe custom resource moet ook los
geactiveerd worden per endpoint — code alleen is niet genoeg.
Bevestigd 2026-08-19 (Bob + Claude samen gedebugd, P1-7 Tab 1
"Persoonlijke agenda"): favorieten_agenda.json gaf op productie
een kale, lege 404 (geen enkele Drupal-header, niets in de
watchdog-log), terwijl exact dezelfde custom.module-code op
devbob prima werkte. De resource zelf stond correct gedefinieerd in
hook_services_resources() (custom_services_resources() in
custom.module) — dat alleen registreert 'm bij de module, maar
een Services-endpoint (bv. flutterdrup) serveert alleen de
resources die daar expliciet voor zijn aangevinkt, een aparte
config/database-instelling per endpoint, los van de module-code.
Die aanvinking was ooit op devbob gedaan (tijdens testen) maar nooit
meegenomen naar productie. Check/fix altijd hier:
https://<domein>/en/admin/structure/services/list/<endpoint-naam>/resources
(of via drush @<site-alias> php-eval "print_r(services_endpoint_load('<endpoint-naam>'));"
— vergelijk de resources-array tussen omgevingen). Symptoom dat
hierop wijst: een endpoint/pad dat op de ene Drupal-omgeving prima
werkt en op de andere een 404 geeft mét een compleet lege body en
zonder de gebruikelijke custom response-headers (bij dit project
bv. x-speed-cache/x-device/x-geoip-*, toegevoegd door de eigen
Aegir/Boa-caching-laag pas ná volledige Drupal-bootstrap) — dat wijst
op "wordt vroeg afgewezen, bereikt zelfs Drupal's eigen watchdog-log
niet", eerder dan een nginx/Cloudflare-routeringsprobleem. Nuttig
debug-recept dat hiertoe leidde: (1) drupalRequest's eigen
print()-debug-output opvangen via adb logcat tijdens een live
test in de app, om de exacte headers/status/body te zien die de app
zelf verstuurt/ontvangt; (2) diezelfde request los met curl -i
natesten op beide omgevingen; (3) om Cloudflare écht te omzeilen
moet je letterlijk 127.0.0.1 in de curl-URL zetten (niet de
echte hostnaam, ook niet met een aangepaste Host-header — Cloudflare
zit op DNS-niveau, een Host-header alleen stuurt de request nog
steeds via Cloudflare's edge); (4) menu_router-tabel checken via
drush php-eval (leeg voor beide omgevingen was hier een belangrijke
aanwijzing dat dit geen kaal hook_menu()-pad was maar via
Services liep); (5) de Drupal watchdog-log (admin/reports/dblog)
vergelijken tussen omgevingen — als de kapotte omgeving zelfs geen
enkele logregel toont terwijl een ander, vergelijkbaar endpoint op
diezelfde omgeving wél volledig doorloopt (inclusief juiste
gebruikersherkenning), sluit dat sessie/cookie/nginx-routing-
problemen vrijwel uit en wijst het naar iets resource-specifieks
binnen Drupal/Services zelf.
Taal/locale volgt het toestel, niet hardcoded Nederlands — zonder
opgeslagen voorkeur (FFLocalizations/_kLocaleStorageKey) valt
MaterialApp.locale terug op de systeemtaal van het toestel; matcht
die met 'en' (tweede taal in FFLocalizations.languages() =
['nl','en']), dan draait de app in het Engels. Er is vrijwel geen
echte Engelse vertaling (zie TASKS.md P1-17, live bevestigd
2026-08-07: van 169 sleutels in
lib/flutter_flow/internationalization.dart zijn er maar 2 waar
nl/en daadwerkelijk verschillen, en één daarvan is een fout
i.p.v. een vertaling). Test dus altijd met het testtoestel/de
emulator op Nederlands (adb shell getprop ro.product.locale
checken), anders lijkt de UI kapot terwijl het eigenlijk gewoon de
ontbrekende Engelse vertaling is.
Drupal 7 Services sessie-auth: sessid + session_name (uit
LoginCall's response) vormen samen de sessie-cookie:
Cookie: <session_name>=<sessid>. token is een los CSRF-token,
alleen nodig bij schrijf-requests (POST/PUT/DELETE), nooit bij GET.
Drupal page cache bootstrapt vóór de sessie — een ooit anoniem gecachete response (bv. een 403) kan session-bootstrap, hooks én custom-module-logging volledig overslaan. Bij twijfel: Drupal-cache legen en opnieuw testen vóór verder debuggen.
Views numeric filter: $view->filter['uid']->value moet
array('value' => $uid) zijn, niet array($uid) — de foute vorm
faalt stil (geen filter toegepast) i.p.v. een error te geven.
Provincie/gemeente blijft het basismodel voor content-scoping
(bevestigd: mensen zoeken primair lokaal). Home is de landelijke
standaard-startpagina zónder verplichte gemeente-keuze vooraf — dat
vervangt het provincie/gemeente-model niet, het is een aanvullende
ingang. De Home-prefixed componenten (HomeUitgaantabelKaartComponent
e.d.) zijn een bewuste kopie van PUitgaanSliderComponent/
UitgaantabelKaartComponent + een eigen cityid-loze API-call — geen
dode code, niet meenemen in opschoonacties.
Favorieten/login zijn P0. Backend-endpoint bestaat al; Drupal 7 views die de respons voeden hebben soms nog aanpassing nodig.
Fout-/leeg-afhandeling bij API-calls: het "geen enkele
FutureBuilder checkt op meer dan !snapshot.hasData"-beeld klopte
niet helemaal (gecorrigeerd 2026-08-04). ApiCallResponse vangt
netwerkfouten zelf op (api_manager.dart, catch (e) => ApiCallResponse(null, {}, -1, ...))
— de Future voltooit dus altijd, geen oneindige spinner. Het
eigenlijke risico was een synchrone crash: bij jsonBody: null
gooit getJsonField(null, ...).toList() een NoSuchMethodError
vóórdat de lijst-widget ooit gebouwd wordt. Bob heeft hiervoor op
HorecagelegenhedenOverzicht een werkend patroon gebouwd
(2026-08-04, rechtstreeks in de builder): bij lege/mislukte data
toont elke tab nu een standaardplaatje i.p.v. te crashen. Exacte
builder-stappen (welke widget/property) nog niet gedocumenteerd —
bij het uitrollen naar andere pagina's (zie TASKS.md P1-1) eerst
navragen/naspeuren i.p.v. blind het Carousel-"Empty List
Widget"-patroon hierboven te kopiëren, want dat lost alleen
itemCount: 0 op, niet per se de null-jsonBody-crash die hier de
kern van het probleem was.
Custom action drupalRequest (lib/custom_code/actions/drupal_request.dart)
geeft sinds 2026-08-17 bij elke fout (non-2xx, timeout, exception)
een lege [] terug, niet meer een {'success': false, ...}-map.
Root cause was een crash op Favorieten (zie TASKS.md): aanroepende
widget-code doet altijd blind .toList() op het resultaat, en
.toList() op een Map bestaat niet (NoSuchMethodError). Er bleek
nergens in de app een consument die de oude success/statusCode/
body-velden van de foutmap daadwerkelijk uitlas (gecheckt vóór de
wijziging), dus dit is veilig gewijzigd voor alle 3 huidige
gebruiksplekken (favorieten_widget.dart x2, lib/kanweg/). Bij
een nieuwe aanroep van drupalRequest toevoegen: ga ervan uit dat
je bij een fout een lege lijst terugkrijgt, geen foutdetails — wil je
die wél (bv. om een eigen foutmelding te tonen), dan moet de
functie-contract opnieuw aangepast worden en alle bestaande
aanroepers gecontroleerd.
drupalRequest is géén centraal punt om lege/null-veldwaarden
op te vangen voor de meeste widgets — bevestigd 2026-08-19 (Claude,
grep -rn "drupalRequest(" lib/). Deze custom action wordt
alleen aangeroepen vanuit favorieten_widget.dart (2x) en het dode
lib/kanweg/. Vrijwel alle andere data (o.a. alles achter P1-6's
valueOrDefault<String>(<bron>, '<placeholder>')-patroon, zoals
HorecagelegenheidoverzichtKaart/evenement_info_widget.dart) komt
binnen via FlutterFlow's ingebouwde declared API Calls
(api_calls.dart, bv. HorecagelegenheidoverzichtCall) — die lopen
nooit langs drupalRequest, en die response heeft geen centraal
transform-punt: elk veld wordt los, inline via getJsonField(...)
op zijn eigen plek in de widget-code uitgelezen. Een "lege string →
spatie"-fix in drupal_request.dart zou dus alleen de 3
Favorieten/kanweg-aanroepen raken — voor de rest blijft de
per-widget Default Value-route (zie P1-6) de enige praktische fix.
API-call cache: true werkt functioneel correct — ApiCallOptions
(lib/backend/api_requests/api_manager.dart) extends Equatable met
params/headers in de props-lijst, dus de in-memory _apiCache
vergelijkt op echte parameterwaarden (deep equality), niet op
object-referentie. Geen reference-equality-bug om je zorgen over te
maken — cache: true is een veilige, lichte performance-fix voor
calls met effectief statische data binnen een sessie (referentie-
lijsten zoals provincies/gemeenten, categorieën). Lost wél alleen
herhaalde identieke calls op (bv. bij een rebuild), niet een
eerste gelijktijdige burst van meerdere verschillende calls (bv. een
eager TabBarView met 6+ tabs die elk hun eigen data ophalen bij
page-load, zie TASKS.md P1-10).