# 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. (Precedent 2026-08-04: twee sessies pakten onafhankelijk dezelfde P0-taak op — Bob moest scheidsrechteren tussen twee stukken werk aan hetzelfde bestand.) ## 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. ## 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). - 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`). Daarna automatisch: 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.) ## 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 **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. **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. **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). **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. ## Domein/architectuurcontext - **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. ## 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.