CLAUDE.md 15 KB

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"<kenmerkende string/tekst>" of een gerichte grep vóórdat je 'm oppakt — niet blind vertrouwen dat "open" betekent "nog niet gefixt". (Precedent: 2026-08-04 bleken 2 "open" P0-taken al op 2026-08-02 gefixt te zijn, git-bevestigd.)
  • Elke taak in TASKS.md heeft een stabiel ID (bv. P0-1) en een Eigenaar:-regelBob (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 addniet 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<DataType> 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.
  3. 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.
  4. 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.

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.
  5. 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.

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: <session_name>=<sessid>. token is een los CSRF-token, alleen nodig bij schrijf-requests (POST/PUT/DELETE), nooit bij GET.
  • Drupal page cache bootstrapt vóór de sessie — een ooit anoniem gecachete response (bv. een 403) kan session-bootstrap, hooks én custom-module-logging volledig overslaan. Bij twijfel: Drupal-cache legen en opnieuw testen vóór verder debuggen.
  • Views numeric filter: $view->filter['uid']->value moet array('value' => $uid) zijn, niet array($uid) — de foute vorm faalt stil (geen filter toegepast) i.p.v. een error te geven.
  • Provincie/gemeente blijft het basismodel voor content-scoping (bevestigd: mensen zoeken primair lokaal). Home is de landelijke standaard-startpagina zónder verplichte gemeente-keuze vooraf — dat vervangt het provincie/gemeente-model niet, het is een aanvullende ingang. De Home-prefixed componenten (HomeUitgaantabelKaartComponent e.d.) zijn een bewuste kopie van PUitgaanSliderComponent/ UitgaantabelKaartComponent + een eigen cityid-loze API-call — geen dode code, niet meenemen in opschoonacties.
  • Favorieten/login zijn P0. Backend-endpoint bestaat al; Drupal 7 views die de respons voeden hebben soms nog aanpassing nodig.
  • Geen enkele "hasError"/netwerkfout-afhandeling bevestigd aanwezig in de app (gecheckt 2026-08-04, hele lib/ doorzocht) — elke FutureBuilder die alleen op !snapshot.hasData checkt, blijft bij een mislukte call voor altijd op de laadspinner hangen. Ga er dus niet van uit dat er ergens al een werkend voorbeeldpatroon staat om te kopiëren — dit moet nog van de grond af ontworpen worden (zie P0-taak in TASKS.md).

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.