# Uitgaanskrant — werkinstructies voor Claude ## Werkdirectory Werk uitsluitend binnen deze projectdirectory (`/home/bob/Projects/ff-app/uitgaanskrant-1qhvtd`). Niet daarbuiten zoeken of scannen — scope alle bestandsoperaties tot dit project. ## Werkwijze binnen een sessie - **Meld bij elke nieuwe stap kort vooraf wat je gaat doen**, vóór je begint (staande voorkeur Bob, 2026-08-04). - Bob bespreekt elke taak in een **nieuwe, aparte chat** — geen eerdere conversatie om op terug te vallen. `CLAUDE.md` + `TASKS.md` zijn samen het volledige geheugen tussen sessies. - **Groepeer opgepakte taken per sessie op FlutterFlow-paginagebied** waar mogelijk (bv. twee taken op dezelfde pagina/component samen oppakken) — minder heen-en-weer-navigeren in de builder, minder tokens. - **`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""` 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.) - **Elke taak in `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. - **⚠️ Concurrency: Bob start elke taak in een nieuwe chat, dus er kunnen meerdere sessies tegelijk actief zijn.** Check vóór je een taak oppakt of de Eigenaar-regel al "— bezig" zegt door iemand anders — zo ja, niet zelfstandig ook gaan bouwen aan hetzelfde bestand/component, vraag Bob eerst wie 'm afmaakt. **Zet zelf "— bezig" zodra je serieus begint — dit geldt óók voor puur onderzoek/ reproductie (bv. een eigen `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.** - **⚠️ Scherpere regel (2026-08-21, Bob expliciet): "— bezig" zetten is geen mentale notitie voor bij het afsluiten — het is een directe `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. - **Start elke sessie met `/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. ## Live pair-fix sessie — Bob doet de builder-klikken, Claude regisseert 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:** 1. Claude geeft exacte builder-stappen voor **precies één taak** uit `TASKS.md`, met taak-ID erbij (bv. "P1-3"). 2. Bob voert uit in eigen browser, meldt "gedaan". 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). 4. 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". 5. **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. 6. **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. 7. **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. 8. 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. ## Sessiegeheugen — afsluitroutine Aan het eind van elke sessie/taak: 1. **`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. 2. **`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. 3. Commit + push de gewijzigde `.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.) ## FlutterFlow-workflow — belangrijk Dit project wordt gebouwd via FlutterFlow (app.flutterflow.io). De FlutterFlow-cloudomgeving is de bron van waarheid, niet deze lokale code-export. - Bob kan **geen** code rechtstreeks bewerken in dit repo. Alle wijzigingen aan pagina's, componenten en modellen moeten via de FlutterFlow-website. Enige uitzondering: custom functions/widgets (`lib/custom_code/`) — ook die voert Bob in via de Custom Code-editor op de website, niet hier lokaal. - Gevolg: directe Edit/Write-wijzigingen aan gegenereerde bestanden worden bij de volgende export overschreven — **niet duurzaam**. Gebruik deze repo om te lezen/ontwerpen/verifiëren (`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. - Werk je rechtstreeks in de builder (Claude in Chrome): commit na elke **afgeronde taak** binnen FlutterFlow's eigen versiebeheer ("main"/"Synced" bovenin), met duidelijke omschrijving. Geen lokale `git commit`. - **Gebruik `mcp__claude-in-chrome__*`** (Bob's gedeelde Chrome), niet de in-app Browser pane. - **Resize de browser niet zelf.** Bob's eigen gedeelde vensters — meld en vraag i.p.v. zelf te resizen. - Bob werkt vaak **gelijktijdig zelf** in dezelfde builder-sessie — check eerst of hij iets claimde ("dat regel ik zelf") voordat je wijzigingen overschrijft. - Check bij een mislukte export/pull eerst het **Issues-paneel** (badge rechtsboven) voordat je een bug bij jezelf zoekt — een rode teller blokkeert *elke* export, ook niet-gerelateerde, en is vaak Bob's eigen work-in-progress. - **De browser-automation naar de FlutterFlow-builder loopt via Bob's eigen, ingelogde Chrome-sessie op zijn machine — dus alleen betrouwbaar terwijl hij actief aanwezig is.** Bevestigd 2026-08-09: terwijl Bob een uur weg was, hing een simpele `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. ## Lokale git-repo + pull/push-workflow 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 ``` - **De `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 ``` (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 `, 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' ...] > ` + 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 -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: ` 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 " /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 --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): 1. `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. 2. `git commit` met duidelijke boodschap. 3. `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 ` voor een ander projectpad dan de default (`ff-app/uitgaanskrant-1qhvtd`). ## Eerst research, dan bouwen 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. ## FlutterFlow-builder: bekende problemen & patronen **Een letterlijke tekst met 1 ingesloten variabele (bv. een JSON-body `{"entity_id": , "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: 1. Selecteer de root **Scaffold**-node in de Widget Tree → klik het kleine **"+"-icoontje naast de node-naam** (niet rechtsklik → Insert Widget, dat menu heeft geen Drawer/AppBar-optie op dit niveau) → zoek "Drawer" (of "App Bar") → kies de kaart. Dit voegt een lege `Drawer`/`AppBar`-slot toe als eigen tree-node, sibling van `body`. 2. Om die slot te **vullen met een custom Project Component** (bv. `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). 3. Een component met een verplichte parameter (bv. `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). 4. **Let op dubbele hamburger-knop:** als de nieuwe `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). 5. Tab-labels die afkappen op een `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` 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. - **Na een edit altijd verifiëren met een screenshot van het gefilterde veld** — een schijnbaar geslaagde `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). - **Zekerste verificatie: "View Code"-paneel**, maar bereik dat via directe navigatie naar `https://app.flutterflow.io/code/?component=` (of `?page=`) 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). - **Update 2026-08-07:** na directe navigatie naar de code-URL toont het middenpaneel soms alleen de kleine component-preview-thumbnail rechtsonder, geen code — klik op die thumbnail (niet op de "Widget"/"Model"-toggle erboven) om de code-editor daadwerkelijk te tonen; kost soms 2 pogingen na een page-reload. Eenmaal in de editor: `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` function-argument) opent een tweede dialoog bovenop de eerste; Confirm/Cancel reageren soms niet zichtbaar. Twee oorzaken, in volgorde van proberen: 1. **Viewport-clipping** (meest voorkomend) — knoppen renderen buiten het zichtbare canvas. Sleep de dialoog omhoog via het handvat bovenin naar een hogere y-positie; de knoppen worden dan zichtbaar en werken gewoon. 2. **Echt bevroren pagina** (alle clicks doen niets) — de widget-wrap staat al server-side, de conditie-edit niet. Herlaad de pagina (`navigate` naar dezelfde URL); wrap blijft staan, conditie moet opnieuw. - **Kortere weg om dit te vermijden:** operator **"Is Set"** i.p.v. "Not Equal To" + lege string — geen Second Value nodig, dus geen tweede dialoog. - Loopt dit na 1-2 pogingen (incl. reload) nog vast: kost dan meer tijd dan Bob het zelf kan doen — meld concreet (component, exacte stappen) en vraag het aan hem. **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. - **Specifiek bevestigd structureel (2026-08-05) voor de "Show Empty List Widget"-checkbox op een Carousel:** dit is geen per-component toeval maar een **systematische blokkade van Claude's browser-automation-viewport** — opgetreden op vier verschillende componenten (`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: 1. Rechtsklik het Image-widget → **Wrap Widget (Ctrl+B)** → **ConditionalBuilder**. 2. IF-conditie: **Conditions → Single Condition** → First Value = de exacte expressie waar de Image's Path-property al aan gebonden was → operator **"Is Set"**. 3. THEN-tak: de bestaande Image (blijft staan na de wrap). 4. ELSE-tak: rechtsklik → Insert Widget → Icon → "image not supported" → eerste Material-resultaat. - **Simpeler alternatief indien van toepassing:** de "Default Variable Value"-toggle op een "Set from Variable"-binding substitueert al bij zowel `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(veld, 'placeholder') != null && valueOrDefault(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. - **Niet blijven proberen na 2-3 pogingen** (elke poging = volledige page-reload + component heropenen + Actions-tab + veld heropenen, kost al snel 10+ tool-calls) — meld concreet aan Bob welke twee argumenten hij moet zetten en op welk veld, dat kost hem in zijn eigen browser seconden. - Voorbeeld hoe dat er dan uitziet (2026-08-05, `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. - **Zelfde bevroren-Confirm-patroon ook bevestigd op een Text-widget's If/Then/Else-conditie** (niet alleen Custom-Function-argumenten) — 2026-08-05 op `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. - **Update 2026-08-07:** de compacte inline-dropdown is **niet betrouwbaar per se buiten AppBar-Row** zoals hierboven gesuggereerd — op `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 se­lecteren → 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. - **In de widget tree zijn `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. - **Snelste manier om die actieketen te dupliceren: rechtsklik op de eerste actie van de bestaande per-tab-flow → "Copy Action Chain" → ga naar de nieuwe On-Page-Load-flow → "Paste Action(s)".** Werkt betrouwbaar, maar **let op deze valkuil:** als de gekopieerde actieketen een Custom Action met een "Action Output Variable Name" bevat (bv. `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. - **"Update App State" met meerdere velden tegelijk wissen (bv. een logout-knop): per veld eigen "Select Update Type"-dropdown, kies "Clear Value" (niet "Set Value").** Add Action → State Management → Update App State → "+ Add Field" (zoekbalk toont alle App State-velden, ook niet-zichtbare zoals `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. - **Binden aan een veld van een lijst-item (bv. een ListView/GridView `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. - **Direct binden aan een bestaande Component Parameter (niet JSON Path) is minder betrouwbaar dan het lijkt — altijd verifiëren via View Code.** Op een non-JSON-Path-binding (klik direct op een Component Parameter-naam in de Set Variable-lijst, geen "Available Options" nodig omdat het al de juiste String is) leek de builder-UI 2026-08-07 een keer de waarde correct te tonen (Value-veld toonde de parameternaam oranje) maar genereerde de export toch `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 --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. - Bereikbaar via het `⌘+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. - **Werkende widget-verwijder-recept (bevestigd 2026-08-09, 5x succesvol op Login-knoppen):** de zwevende toolbar boven een geselecteerd canvas-widget (prullenbak-icoon) lijdt aan hetzelfde coördinaat-probleem en klikt daardoor vaak mis (geen zichtbare fout, gewoon geen effect). Betrouwbaarder: **rechtsklik op de Widget Tree-rij** (niet het canvas) → tekst-menu-item **"Remove Widget"** (met woorden, niet het icoon) aanklikken. Werkte 5x op rij zonder falen zodra eenmaal de juiste rij geselecteerd was (zie hieronder voor selectie). Een simpele `Delete`-toetsaanslag wordt door de Bash/computer-permissie-classifier geblokkeerd als destructieve actie — niet bruikbaar, gebruik het menu-item. - **Selectie-kalibratietruc:** bij twijfel over de klik-offset, klik eerst op een duidelijk identificeerbare rij (bv. de eerste) en vergelijk in het rechterpaneel welke rij *echt* geselecteerd werd. Het verschil (in pixels) tussen bedoelde en werkelijke rij is **op dat moment constant** voor de rest van diezelfde paneel-render — pas die vaste correctie toe op volgende kliks i.p.v. te blijven gokken. Geldt zowel voor links- als rechtsklik. - **Tekst typen in het rechterpaneel (bv. Hint Text/vertaalvelden) is op Claude's viewport structureel niet mogelijk gebleken (2026-08-09, login-hintText-veld).** Klik, triple-click, Ctrl+A+typen, losse `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. - **Visibility → "Conditional"-toggle (rechterpaneel, boven op elk widget) is voor Claude structureel niet betrouwbaar te klikken — apart bevestigd patroon, niet hetzelfde als de checkbox-clipping hierboven.** Bevestigd 2026-08-14 op `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.** - **Testen met een niet-Nederlandse systeemlocale: gebruik `adb shell cmd locale set-app-locales --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: 1. Voeg eerst een **Backend Query** (database-icoon) toe aan de ListView/GridView zelf, type "API Call" → kies de call. 2. Voeg een item-template-widget toe als kind (bv. `ListTile`, of een `Row`/`Container` naar smaak) via Insert Widget. 3. Selecteer de ListView/GridView zelf opnieuw → klik het **4e icoontje (Generate Dynamic Children)** → **Variable Name** (vrije naam, wordt de loop-itemvariabele) → **Value** → klik het icoontje naast "Value" → Set from Variable → kies de API-call-response als bron → **Available Options → "No Further Changes"** (géén Predefined Path/JSON Path hier — je wil de **rauwe array**, niet één geëxtraheerd veld) → Confirm → **Save**. Een bevestigingsdialoog volgt ("This widget will now generate its children dynamically... edit the first child to set how every child should look") → Ok. De canvas toont meteen N herhaalde design-time placeholder-rijen. 4. **Cruciaal restpunt:** de velden die je vóór stap 3 al op het item-template gebonden had (bv. via de Backend Query direct) wijzen nog naar de **hele respons**, niet naar het loop-item — moeten **opnieuw** gebonden worden: Set from Variable → bron is nu de nieuwe **loop-itemvariabele** (naam uit stap 3, verschijnt als eigen Source-regel) → Available Options → **"JSON Path"** → pad **relatief aan het item** (dus `$.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). 5. Voor een tap-actie per item (bv. navigeren met het item's ID): zie de Actions-tab-notitie hierboven — Value van de Navigate-To-parameter ook via Set from Variable → loop-itemvariabele → JSON Path. Geverifieerd via verse export: genereert een correcte `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). ## Domein/architectuurcontext - **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:///en/admin/structure/services/list//resources` (of via `drush @ php-eval "print_r(services_endpoint_load(''));"` — 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: =`. `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(, '')`-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). ## Samenwerken met Bob - Bob is de enige developer/eigenaar, werkt vaak **zelf gelijktijdig** in dezelfde builder-sessie. Neem niet aan dat elke wijziging van jou komt. - Prioriteit: "eerst een werkende app live, daarna features" — P0 weegt zwaar boven P1 en P2. Bob herprioriteert soms fors zelf (bv. login+favorieten van P2 naar P0) — volg dat exact. - Claimt Bob een taak terug ("dat regel ik zelf") — stop daar direct mee en pak iets anders onafhankelijks op. - Kost iets veel tijd door handmatige builder-acties (vastzittende dialogen, geclipte controls): na 1-2 serieuze pogingen stoppen en concreet aan Bob voorstellen dat hij het zelf doet (exacte stappen). - Rapporteer nieuw gevonden bugs (vooral op een pagina waar Bob net zelf op zit) direct en duidelijk, niet pas in een latere samenvatting.