# Uitgaanskrant — werkinstructies voor Claude ## Werkdirectory Werk uitsluitend binnen deze projectdirectory (`/home/bob/Projects/ff-app/uitgaanskrant-1qhvtd`). Niet daarbuiten zoeken of scannen (bijvoorbeeld geen `find /` over het hele bestandssysteem) — scope alle bestandsoperaties tot dit project. ## Sessiegeheugen — lees dit eerst Bob bespreekt elke taak in een **nieuwe, aparte chat**. Dit bestand en `TASKS.md` zijn daarom het geheugen tussen sessies — er is geen eerdere conversatie om op terug te vallen. **Vaste afsluitroutine, aan het eind van elke sessie/taak:** 1. Werk `TASKS.md` bij: **een afgeronde taak wordt volledig verwijderd uit de lijst** (niet gearchiveerd in een "Afgerond"-sectie — dat is onnodige context/kosten voor toekomstige sessies). Zorg dat elke resterende open taak zelfstandig te begrijpen is (concreet, met bestandspad/context) zonder de chat gelezen te hebben waarin hij ontstond. 2. Werk dit bestand (`CLAUDE.md`) bij: **alleen** toevoegen als het een blijvend herbruikbaar inzicht is (een conventie, een architectuurkeuze, een bekende valkuil/bug-patroon) dat een latere sessie anders opnieuw zou moeten uitzoeken. Geen sessieverslag, geen changelog van wat er gedaan is, geen herhaling van redeneringen. Ruim verouderde/dubbele info op in plaats van 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 — alleen de export/build-cyclus hieronder draaien als er ook echt app-code is gewijzigd). ## 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. - De gebruiker kan **geen** code rechtstreeks genereren of bewerken in dit repo. Alle wijzigingen aan pagina's, componenten en modellen moeten via de FlutterFlow-website gebeuren. - De enige uitzondering is **custom functions/widgets** (`lib/custom_code/`) — en ook die voert de gebruiker in via de Custom Code-editor op de FlutterFlow-website, niet door hier rechtstreeks bestanden te bewerken. - Gevolg: directe Edit/Write-wijzigingen aan gegenereerde bestanden (pagina's, blocks, components, models, `lib/flutter_flow/*` e.d.) worden bij de volgende export/sync vanuit FlutterFlow overschreven. Zulke bestanden hier aanpassen is dus **niet duurzaam**. - Werkwijze: gebruik deze lokale repo om code te lezen, logica te ontwerpen en te verifiëren (bijv. met `flutter analyze`) — maar vertaal het eindresultaat naar concrete, stapsgewijze instructies voor wat de gebruiker in de FlutterFlow-builder (of de Custom Code-editor) moet doen, of voer het zelf uit via browser-automation (zie hieronder). Ga er niet van uit dat een lokale bestandswijziging behouden blijft. - Als er via de Chrome-browser (Claude in Chrome) rechtstreeks in de FlutterFlow-builder wordt gewerkt: maak na elke **afgeronde taak** (niet na elke losse klik/handeling) een **commit binnen FlutterFlow zelf** (het eigen versiebeheer, te zien bovenin de builder als "main"/"Synced"), met een duidelijke omschrijving van wat er is veranderd. Dit is geen lokale `git commit`. - **Gebruik `mcp__claude-in-chrome__*` (Bob's eigen, gedeelde Chrome), niet de in-app Browser pane** — dat is de afgesproken tool voor builder-automation. - **Resize de browser niet zelf** (`resize_window` e.d.) — dit is Bob's eigen gedeelde vensters. Als iets buiten beeld valt, meld dat en vraag het aan Bob i.p.v. zelf te resizen. - Bob werkt vaak **gelijktijdig zelf** in dezelfde builder-sessie. Ga er niet van uit dat elke wijziging die je in de widget tree/`git diff` ziet van jezelf komt — check eerst of hij iets claimde ("dat regel ik zelf") voordat je het overschrijft. - Check bij een mislukte export/pull eerst het **Issues-paneel** (badge-icoon rechtsboven, naast de sync-checkmarks) voordat je een bug bij jezelf zoekt — een rode teller daar blokkeert *elke* export, ook voor niet-gerelateerde correcte wijzigingen, en is vaak Bob's eigen work-in-progress. ## Lokale git-repo + pull/push-workflow De projectdirectory zelf is een schone git-repo met `origin` op `ssh://gogs.digitalforce.tv:2222/Uitgaanskrant.com/flutterflow.git`, branch `master`. Dit is een eigen repo van de gebruiker, los van de grotere/rommelige repo die hoger in de mappenstructuur staat (die met AndroidStudioProjects, Flutter-SDK-installaties e.d.) — commits en pushes horen hier, in de projectdirectory zelf. Vaste workflow na elke afgeronde taak (staand akkoord, geen aparte bevestiging per keer nodig): niet los `flutterflow export-code` draaien, maar het bestaande script gebruiken: ``` 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.** De Bash-tool draait niet-interactief, dus `~/.bashrc` (waar `fvm`/`flutterflow` normaal op PATH komen) wordt niet geladen. Zonder deze prefix faalt het script stil met "fvm: command not found" / "Prerequisites not met, skipping execution" — geen harde error, dus makkelijk te missen. - Dit script is interactief (device-run + hot-restart-loop) — moet via Bash met `run_in_background: true`. Volg de output tot minimaal "All done!" (export gelukt) én idealiter een succesvolle app-launch op de emulator (`Launching lib/main.dart...`, geen nieuwe `EXCEPTION CAUGHT BY RENDERING LIBRARY` t.o.v. bekende issues in `TASKS.md`) voordat je verder gaat. Daarna automatisch: 1. `git status --short` bekijken, dan `git add` — **niet blind `-A`**. Voeg alleen daadwerkelijke FlutterFlow/app-wijzigingen toe (`lib/`, `android/`, `ios/`, `pubspec*`, `.gitignore` e.d.). Sluit `.claude/` uit (staat in .gitignore) — dat is sessiestate van Claude Code, geen app-code. Wijzigingen die niet van jezelf zijn (Bob's eigen concurrent werk) horen gewoon mee in dezelfde commit — dit is één gedeeld project. 2. `git commit` met een duidelijke boodschap. 3. `git push` (of `git push -u origin master` als tracking nog niet staat). **Valkuil: `flutterflow export-code` overschrijft `.gitignore` bij elke export terug naar FlutterFlow's eigen standaardversie.** Voeg dus na elke export, vóór je staged, deze regel weer toe aan `.gitignore` als hij ontbreekt: ``` # Claude Code session state (not app code) .claude/ ``` (`CLAUDE.md` zelf overleeft een export altijd — alleen bij een volledig verwijderde en opnieuw gepulde projectmap zou het verdwijnen. Check voor de zekerheid toch even of het bestand er nog is.) ## Eerst research, dan bouwen Voordat je aan een niet-triviale taak begint (een bugfix, een nieuw patroon, een integratie): zoek eerst kort op internet naar bestaande oplossingen/patronen voordat je het zelf helemaal uitvindt via trial-and-error in de builder. Geldt niet voor triviale/mechanische herhaling van een patroon dat al bevestigd werkt (zie hieronder). ## FlutterFlow-builder: bekende problemen & patronen **Geneste "Set Variable"-dialoog kan lijken vast te zitten.** Bij het instellen van een conditie (`ConditionalBuilder`, Visibility → Conditional) op een niet-triviaal type (JSON Path, API-response-veld) opent een tweede dialoog bovenop de eerste. Confirm/Cancel op die binnenste dialoog reageren soms niet zichtbaar. Twee bekende oorzaken, in volgorde van proberen: 1. **Viewport-clipping** — de knoppen renderen buiten het zichtbare canvas, niet echt vast. Sleep de dialoog omhoog via het handvat bovenin (kleine grijze balk) naar een hogere y-positie; de Confirm/Cancel-knoppen worden dan zichtbaar en werken gewoon. 2. **Echt bevroren pagina** (alle clicks, ook op onbetrokken plekken, doen niets) — de widget-wrap zelf is al server-side opgeslagen, maar de conditie-edit niet. Los op door de pagina te herladen (`navigate` naar dezelfde URL); de wrap blijft staan, de half-ingevulde conditie moet opnieuw. - **Kortere weg om deze dialoog te vermijden:** gebruik als operator **"Is Set"** in plaats van "Not Equal To" + lege string — dan is er geen Second Value nodig en dus geen tweede geneste dialoog. - Als dit na 1-2 pogingen (incl. de reload-poging) nog vastloopt: dit kost dan meer tijd dan Bob het zelf kan doen — meld het concreet (component, precieze stappen) en vraag het aan hem i.p.v. te blijven proberen. **Component Name kan per ongeluk overschreven worden.** Vlak na paginanavigatie kan een klik bedoeld voor het "Search properties..." veld in plaats daarvan op het Component Name-veld landen (focus/ z-order race condition), en typen hernoemt dan stilletjes het hele component. Mitigatie: na een click-before-type altijd eerst een screenshot om focus te bevestigen, zeker vlak na navigatie. Herstel: rechtsklik component in de zoekresultatenlijst → "Rename Component". **Rechterpaneel kan te breed zijn voor de viewport.** Sommige controls (Expansion segmented control, Visibility → Conditional expression builder) renderen soms deels buiten beeld — dit is geen `resize_window`-probleem (zie hierboven: niet zelf resizen). Na 1-2 bevestigde pogingen stoppen en aan Bob vragen. **Patroon: lege/ontbrekende afbeeldings-URL laten 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 wordt gedaan, dus `errorWidget`/ "Show Error Image on Failure" vangt dit **niet** (dat vangt alleen échte laadfouten zoals 404's). Werkende fix, toegepast op 9 van de ~10 bekende instanties (zie `TASKS.md`): 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 (component-parameter, JSON Path, of custom-function-resultaat via "Evenement Response" → API Response Options → JSON Body → JSON Path, bv. `$[0].logo`) → operator **"Is Set"**. 3. THEN-tak: de bestaande Image (blijft staan na de wrap). 4. ELSE-tak: rechtsklik → Insert Widget → Icon → zoek "image not supported" → eerste Material-resultaat. - **Simpeler alternatief indien van toepassing:** de "Default Variable Value"-toggle op een willekeurige "Set from Variable"-binding substitueert al bij zowel `null` als lege string (bevestigd via in-builder tooltip: "if the resulting value is null or empty") — géén ConditionalBuilder nodig. Werkt hier alleen niet voor Image-widgets met `Image Type: Network`, omdat er nog geen gehoste fallback-afbeeldings-URL bestaat in dit project. Mocht die er ooit komen (bv. een default-logo op de FlutterFlow-CDN of Drupal-server): gebruik dan Default Variable Value + "Show Error Image on Failure" i.p.v. ConditionalBuilder — sneller te bouwen. **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` (zelfde stijl als de gedeelde header), geen losse styling nodig. Werkt niet op iconen die embedded zitten als `suffixIcon` van een `TextFormField` (bv. Login-pagina's wis-/toon-wachtwoord-iconen — geen losse wrapbare tree-node). **API-call headers: check op hardcoded literals i.p.v. `[varname]` templates.** Als een API-call via curl wél werkt maar vanuit de app/ Response & Test-panel niet, controleer het Headers-tabblad van de call-configuratie — een handmatig ingeplakte testwaarde (bv. een Cookie-header) kan per ongeluk blijven staan i.p.v. de `[sessionname]=[sessionid]`-template. 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 je verder debugt. - **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 is en blijft het basismodel voor content-scoping (bevestigd door Bob: mensen zoeken primair lokaal). Home is de landelijke standaard-startpagina zónder verplichte gemeente-keuze vooraf (task #31, afgerond) — dat vervangt niet het provincie/ gemeente-model, het is een aanvullende ingang. - Favorieten/login zijn P0. Backend-endpoint bestaat al; Drupal 7 views die de respons voeden hebben soms nog aanpassing nodig. ## Samenwerken met Bob - Bob is de enige developer/eigenaar en werkt vaak **zelf gelijktijdig** in dezelfde FlutterFlow-builder-sessie. Neem niet aan dat elke wijziging van jou komt. - Prioriteit: "eerst een werkende app live, daarna features" — P0 (blokkeert livegang) weegt zwaar boven P1 (polish/performance) en P2 (nieuwe features). Bob herprioriteert soms fors zelf (bv. login+ favorieten van P2 naar P0) — volg dat exact. - Als Bob een taak terugclaimt ("dat regel ik zelf") — stop daar direct mee en pak iets anders onafhankelijks op. - **Als iets veel tijd kost door handmatige builder-acties** (vastzittende dialogen, geclipte controls e.d.): na 1-2 serieuze pogingen stoppen en concreet aan Bob voorstellen dat hij het zelf doet (met exacte stappen) i.p.v. door te blijven proberen. - Browser niet zelf resizen (zie boven). - Rapporteer nieuw gevonden bugs (vooral op een pagina waar Bob net zelf op zit) direct en duidelijk, niet pas in een latere samenvatting.