Przeglądaj źródła

P2-6: zoekveld + filter gekoppeld op 5 van 6 tabs (Copy3)

Stap 1-3 af en stap 5 op Activiteiten/Cultuur/Eetgelegenheden/Overnachten/
Uitgaan. Elke stap geverifieerd met een verse export; live pagina functioneel
ongewijzigd, dart analyze 0 errors.

Resteert op Copy3: tab Verhuur-catering, en Cultuur's categoriePad staat op
$categorie i.p.v. $.categorie.

CLAUDE.md: vier herbruikbare bevindingen vastgelegd - nullable page state aan
een verplicht functie-argument binden crasht (analyze ziet het niet), het
Local-Page-State-paneel slaat wijzigingen niet op, het zoekveld in de
Set-Variable-dialoog filtert over alle bronnen, en het tree-zoekveld is de
betrouwbaarste navigatie bij herhaald widgetwerk.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
bob 1 tydzień temu
rodzic
commit
391d174993
2 zmienionych plików z 197 dodań i 14 usunięć
  1. 48 0
      CLAUDE.md
  2. 149 14
      TASKS.md

+ 48 - 0
CLAUDE.md

@@ -1160,6 +1160,54 @@ is veel toleranter en draait vaak door terwijl `export-code` weigert —
 "de build doet het toch gewoon" is dus geen bewijs dat de teller
 onschuldig is.
 
+**Bind een verplicht (non-nullable) String-argument van een Custom Function
+NOOIT aan een nullable Page State — dat genereert een `!` en crasht bij de
+eerste build.** Bevestigd 2026-09-05 (`filterHorecagelegenheden` op
+`HorecagelegenhedenOverzichtCopy3`): het argument `zoekterm` (type `String`)
+gekoppeld aan de page state `zoektermActiviteiten` (`String?`, initialiseert op
+`null`) leverde `_model.zoektermActiviteiten!` op → `Null check operator used
+on a null value` zodra de tab bouwt. **`dart analyze` ziet dit niet** (het is
+geldige Dart), dus een schone analyse is hier geen bewijs. **Fix:** bind aan
+**Widget State** van het invoerveld zelf (`TextField 1`) — dat geeft
+`_model.textController1.text`, non-nullable. De page state blijft wel nodig als
+rebuild-trigger: zijn `safeSetState` is wat de lijst opnieuw laat filteren.
+
+**Het "Local Page State Variables"-paneel (pagina-root → 5e icoontje) slaat
+wijzigingen structureel NIET op — ook niet in Bob's eigen browser.** Bevestigd
+2026-09-05, vier verschillende bewerkingen: een veld **hernoemen**, **Nullable**
+uitvinken, **Initial Field Value** vullen, en de **"Default Variable Value"** in
+de Set-Variable-dialoog. Alle vier tonen de nieuwe waarde in de UI, de header
+meldt "Synced", en een verse export toont onveranderd de oude staat. Reken er
+dus op dat een page-state-veld zijn oorspronkelijke naam en nullability houdt;
+plan er geen opschoning omheen. (Dit spreekt de eerdere notitie over
+`createPlaatsID` → `createHorecagelegenheidNid` tegen — die rename werkte in
+2026-09-03 wél; blijkbaar niet betrouwbaar reproduceerbaar.)
+
+**Het zoekveld in de "Set Variable"-dialoog filtert over álle bronnen, ook
+Widget State en Custom Functions.** Bevestigd 2026-09-05: typ gewoon
+`TextField 1` of `filterHoreca` en de lijst reduceert tot die ene bron met de
+optie er direct onder. Veel sneller en betrouwbaarder dan de bron uitklappen en
+de bekende hover-truc gebruiken om een leeg gerenderde rij zichtbaar te maken.
+**Let op:** de eerste keer typen landt vaak niet (het veld krijgt pas focus na
+de klik) — klik, typ, en verifieer met een `zoom`; reken standaard op 2
+pogingen per invoerveld in deze dialoog.
+
+**"Set Function Arguments" met 5 argumenten is wél volledig bereikbaar** —
+anders dan het `evenementCreate`-geval verderop. Twee dingen samen: sleep de
+**onderrand** van de dialoog omlaag (groeit ±70px per sleep), en **klap elk
+afgehandeld argument in via zijn chevron**, dan schuift het volgende in beeld.
+Zo zijn op 2026-09-05 vijf keer achtereen alle 5 argumenten gezet. Vergeet na
+**Confirm** in de dialoog niet de aparte **Save**-knop in het rechterpaneel —
+zonder die tweede klik wordt er niets weggeschreven.
+
+**Het tree-zoekveld ("Search for widget...") is de betrouwbaarste navigatie bij
+herhaald werk op gelijksoortige widgets.** Typ bv. `StaggeredView` en de boom
+reduceert tot alleen die nodes, in documentvolgorde (dus tabvolgorde) — dat
+omzeilt zowel het niet-scrollende boompaneel als de bekende klik-offset. Zonder
+dit filter sprong de selectie tijdens deze sessie meermaals naar de pagina-root
+(rechterpaneel toont dan opeens "Local Page State Variables") — onschadelijk,
+maar je moet het wel opmerken vóór je verderklikt.
+
 **Een Custom-Action-argument van het type `List<String>` dat als fout
 "argument X is not set properly" gemeld wordt: klap in de Inline
 Function-dialoog "Return Type (Optional)" open en druk dan pas

+ 149 - 14
TASKS.md

@@ -2252,7 +2252,7 @@ overgeslagen, Bob's eigen Sort/Datatype-experiment op die pagina loopt
 nog (zie P2-6); oppakken zodra dat is afgerond, zelfde recept
 (kopieer de AdBanner-node vanaf een van de 3 al werkende pagina's).
 
-**P2-6 · KLAAR OM UIT TE VOEREN — begin hier. Eigenaar: Claude.**
+**P2-6 · KLAAR OM UIT TE VOEREN — begin hier. Eigenaar: Claude — bezig (sessie 2026-09-04b, builder via Chrome, bouwt op `HorecagelegenhedenOverzichtCopy3`).**
 
 > ### Uitvoerplan voor de volgende sessie (opgesteld 2026-09-04, alles hieronder is getest)
 >
@@ -2318,6 +2318,87 @@ nog (zie P2-6); oppakken zodra dat is afgerond, zelfde recept
 > met `Unexpected error from the server` (3x achter elkaar gezien op
 > 2026-09-04), en lukt daarna gewoon. Even wachten, niet gaan zoeken.
 
+> ### Stand na 2026-09-05 — stappen 1-3 klaar, stap 5 op 5 van de 6 tabs
+>
+> Alles hieronder is per stap geverifieerd met een **verse export**. De live
+> `HorecagelegenhedenOverzicht` is functioneel ongewijzigd; `dart analyze` op
+> de verse export geeft **0 errors**.
+>
+> **Klaar:**
+> - **Stap 1+2** — één `TextField` in de root-`Column` van Copy3, boven de
+>   `TabBar`. Insertie was puur additief (0 verwijderde regels). FlutterFlow
+>   hernummerde de controllers: dit veld is **`textController1`**.
+> - **Stap 3** — `On Change` → Update Page State `zoektermActiviteiten`
+>   (= `_model.textController1.text`). ⚠️ FlutterFlow zet daar zelf een
+>   **`EasyDebounce` van 2000 ms** op; veel te traag voor een lokaal filter,
+>   moet naar ~300 ms.
+> - **Stap 5 op 5 tabs**: Activiteiten, Cultuur, Eetgelegenheden,
+>   Overnachten, Uitgaan draaien op
+>   `filterHorecagelegenheden(alleXxx, textController1.text,
+>   categorieFilterActiviteiten, '$.titel', '$.categorie')`.
+>
+> **Nog te doen — twee concrete puntjes, allebei op Copy3:**
+> 1. **Tab 6 (Verhuur, catering)** — de enige `StaggeredView` die nog op de
+>    rauwe API-respons staat. Zelfde recept als de andere vijf, met
+>    `items` = `alleVerhuurCatering`.
+> 2. **Cultuur's `categoriePad` staat op `$categorie` i.p.v. `$.categorie`**
+>    (punt ontbreekt). `filterHorecagelegenheden` strippt alleen een `$.`-
+>    prefix, dus de sleutel wordt letterlijk `$categorie` → het categoriefilter
+>    matcht op die tab nooit. Zoeken op naam werkt er wél gewoon.
+>
+> **⚠️ `categorieFilterActiviteiten` is bewust het GEDEELDE veld voor alle zes
+> tabs** — er komt één dropdown boven de `TabBar`, niet één per tab. Gebruik
+> dus niet `categorieFilterCultuur` e.d. De naam klopt niet meer, maar
+> hernoemen lukt niet (zie hieronder).
+>
+> **Werkend recept per `StaggeredView`** (bewezen op 5 tabs):
+> 1. Zet het tree-zoekveld op **`StaggeredView`** — dan staan alle 6 grids
+>    onder elkaar, in tabvolgorde. Veruit de betrouwbaarste navigatie.
+> 2. Node selecteren → **4e icoontje** (Generate Dynamic Children) → potlood
+>    naast **Value** → potlood naast **Variable** → zoekveld `filterHoreca` →
+>    **Custom Functions** → **hover** op de rij eronder (rendert pas bij hover)
+>    → `filterHorecagelegenheden`.
+> 3. Argumenten: `items` = Page State `alleXxx` (*No Further Changes*);
+>    `zoekterm` = **Widget State → `TextField 1`**; `categorieFilter` = Page
+>    State `categorieFilterActiviteiten`; `titelPad` = `$.titel`;
+>    `categoriePad` = `$.categorie`.
+> 4. **Confirm** in de dialoog **én** daarna **Save** in het rechterpaneel.
+>
+> **Praktische valkuilen die deze sessie tijd kostten:**
+> - **Bind `zoekterm` NOOIT aan de page state `zoektermActiviteiten`.** Die is
+>   nullable en initialiseert op `null`; FlutterFlow genereert dan
+>   `_model.zoektermActiviteiten!` → **gegarandeerde crash** zodra de tab
+>   bouwt, en `dart analyze` ziet dat niet. Widget State geeft
+>   `_model.textController1.text`, non-nullable.
+> - **Het zoekveld in de Set-Variable-dialoog filtert over álle bronnen**,
+>   inclusief Widget State. Typ gewoon `TextField 1` — veel sneller dan
+>   Widget State uitklappen en hoveren.
+> - **De eerste klik op een icoon/potlood/zoekveld landt vaak niet** (paneel
+>   herberekent nog). Reken op 2 pogingen per actie en verifieer met een zoom.
+> - **Het argumentenpaneel scrollt niet**, maar je komt er wel bij: onderrand
+>   van de dialoog omlaag slepen én elk afgehandeld argument inklappen via
+>   zijn chevron.
+> - Bij tab 6 bleef de bronnenlijst na ±10 pogingen dichtklappen zodra
+>   "Custom Functions" uitgeklapt was — dialoog sluiten/heropenen hielp niet.
+>   Daar is gestopt; in Bob's eigen browser is dit het proberen waard.
+>
+> **⚠️ Het "Local Page State Variables"-paneel slaat wijzigingen niet op —
+> ook niet bij Bob.** Getest: veld hernoemen, Nullable uitvinken, Initial
+> Field Value vullen, en de "Default Variable Value" in de Set-Variable-
+> dialoog. Alle vier tonen de nieuwe waarde in de UI, header meldt "Synced",
+> maar een verse export toont de oude staat. Daarom houden de velden hun
+> misleidende `...Activiteiten`-namen.
+>
+> **Ook nog open:** stap 4 (categoriedropdown boven de `TabBar` + de custom
+> function die unieke `categorie`-waarden uit de actieve dataset haalt),
+> stap 7 (route omzetten), de debounce, en de cosmetica van het zoekveld
+> (vaste `width: 200`, geen hint-tekst, geen wis-kruisje).
+>
+> **Openstaande vraag aan Bob:** `Max Items` staat per tab op **25** (bestond
+> al vóór deze sessie). Filteren gebeurt vóór het afkappen, dus zoeken werkt —
+> maar zónder zoekterm zie je 25 van de volledige dataset die
+> `fetchAlleHorecagelegenheden` juist ophaalt. Limiet weghalen of laten staan?
+
 **Achtergrond en bewijs (Eigenaar: Claude)** (niet meer 'bezig' — de de-risk-test is
 gedaan, zie het kader hieronder; stappen 3/4/6 staan nog open).
 
@@ -2635,6 +2716,12 @@ liggen tot Bob erop terugkomt — geen eigen taak-ID.)*
 2026-09-04).** Claude mag deze lijst wél verifiëren en actueel houden;
 het daadwerkelijke verwijderen doet Bob zelf. Opschonen:
 
+**✅ Twee items hiervan zijn 2026-09-04 door Bob afgehandeld, bevestigd
+via verse export:** `HomeUitgaantabelKaartComponentCopy` staat nu als
+`lib/kanweg/kanweg_home_uitgaantabel_kaart_component_copy/`, en
+`lib/components/uitgaantabel_kaart_widget.dart` (+ `_model`) is uit de
+export verdwenen. Streep die twee weg in de inventarisatie hieronder.
+
 **⚠️ Verse, complete inventarisatie 2026-09-04 (Claude, code-only op een
 verse export in de projectmap). Vervangt de losse, deels achterhaalde
 bevindingen hieronder — gebruik deze lijst, niet de oudere bullets.**
@@ -3951,11 +4038,34 @@ blijven ook ongewijzigd; de app mag fijnmaziger zijn dan de site.
 De tweede helft (gelegenheiddetail `Info/Links/Bezorgen` tegenover de
 site's `Info/Opening/Map`) is niet opgepakt en staat hieronder los.)*
 
-**P2-16b · Eigenaar: Bob** (rest van P2-16). Op de gelegenheiddetail
-heeft de app `Info / Links / Bezorgen`, de site `Info / Opening / Map`.
-Openingstijden en kaart verdienen een eigen tab, Links kan onder Info.
-Let op: `evenement_horecagelegenheid` noemt dezelfde tab al wél
-"Openingstijden" — die twee schermen spreken elkaar nu tegen.
+*(P2-16b afgerond 2026-09-04 — Bob, builder. **De echte vondst was niet
+een verkeerde tabnaam maar ontbrekende informatie:** `openingstijden` en
+`openingstijdenuitzondering` stonden nergens op
+`HorecagelegenheidCurrent`, terwijl de API ze wel levert. Beide
+toegevoegd, met Visibility-guard op `openingstijden`.
+**Bewust niet gedaan:** (1) de Links-tab opheffen — Bob houdt 'm; de app
+mag fijnmaziger zijn dan de site (zelfde redenering als bij P2-16's
+"Activiteiten" en P0-11's drawer), en de Info-tab zou onoverzichtelijk
+worden met vijf links erbij. (2) "Map" als eigen tab — er staat al een
+`GoogleMapsIconButtonWidget` in de Info-tab (P2-14), en een echte kaart
+vergt een Maps API-key + billing, wat bewust P2-2/v2 is.
+**Bob's kanttekening (2026-09-04):** look & feel van site en app moeten
+t.z.t. in één keer synchroon getrokken worden, niet scherm voor scherm —
+losse "maak het net als de site"-punten dus met die bril bekijken.)*
+
+*(P2-16b restpunt afgerond 2026-09-04 — `openingstijdenuitzondering`
+heeft nu een `Is Set`-guard (regel 693). Daarmee zijn alle 21 API-velden
+op `HorecagelegenheidCurrent` afgedekt.)*
+
+⚠️ **Guard-audit `HorecagelegenheidCurrent` (2026-09-04, verse export):
+alle 21 API-velden zijn afgedekt op dat ene na.** Let bij toekomstig
+zoekwerk op twee valkuilen: er staan **twee** guard-patronen door elkaar
+— `if (getJsonField(...) != null)` (nieuwer, JSON-Path) en
+`if (EstablishmentInfoCall.establishmentX(...) != null && ... != '')`
+(P0-8) — én drie getters hebben een **typfout** in hun naam:
+`establshmentTitel`, `establshmentKvk`, `establshmentBestellink` (zonder
+de **i**). Een grep op "establishment" mist die drie en suggereert dan
+ten onrechte een ontbrekende guard.
 
 **P2-22 · Eigenaar: Bob (P2-15-werk; blokkeert de export NIET).**
 Vier openstaande fouten in het Issues-paneel, stand 2026-09-03. Geen
@@ -4043,14 +4153,39 @@ staat nu dus op **beide** plekken in plaats van nergens. Terugzetten is
 één swatch: `article_sharp` past bij "Cultuur & Info" én bij het
 krantenmerk. Bob is hierop gewezen en houdt het voorlopig zo.
 
-**P2-19 · Eigenaar: Bob.** `nav.dart`:129 stuurt een ingelogde
-gebruiker naar het provincie/gemeente-keuzescherm en een uitgelogde
-naar Home; de site doet het omgekeerd (iedereen begint landelijk,
-kiezen mag later). Nu voelt inloggen als een drempel i.p.v. een
-voordeel. Fix: altijd Home als startpunt, plus de gekozen gemeente als
-klikbare regel in de header ("Amsterdam · wijzigen") — dat beantwoordt
-meteen de vraag "welke gemeente zie ik hier eigenlijk?", die op de
-lijstschermen nergens beantwoord wordt.
+*(P2-19 afgerond 2026-09-04 — Bob, builder. Iedereen begint nu landelijk
+op Home, zoals de site; voorheen kreeg een ingelogde gebruiker het
+provincie/gemeente-keuzescherm, waardoor inloggen als drempel voelde.
+App Settings → **Entry Page én Logged In Page allebei op `home`**.
+FlutterFlow toont daarbij "Warning: Entry Page and Logged In Page should
+be different" — **dat is een generiek advies, geen fout**: de waarde
+slaat gewoon op, geverifieerd via verse export
+(`loggedIn ? HomeWidget() : HomeWidget()`, ook in de `errorBuilder`).
+Let op dat "Entry Page" de tak voor **uitgelogde** bezoekers is en
+"Logged In Page" die voor ingelogde — bij de eerste poging waren die
+verwisseld, waardoor een nieuwe bezoeker juist een verplicht
+keuzescherm als eerste indruk kreeg.
+Tegelijk gebouwd: `HeaderButtonsComponent` toont nu
+`FFAppState().gemeenteSelectNaam` met On-Tap
+`pushNamed(SelectprovinciegemeenteWidget.routeName)`, zodat zichtbaar is
+wélke gemeente actief is en hoe je die wisselt.)*
+
+**P2-19 restpunt · Eigenaar: Bob.** Nu iedereen op Home start, is nergens
+zichtbaar **welke gemeente actief is** — de lijstschermen tonen dat niet,
+en de gemeentekeuze is alleen nog via de drawer ("Zoek stad", bovenaan)
+te bereiken. Voorstel uit de merkanalyse: de gekozen gemeente als
+klikbare regel in de header, bv. "Amsterdam · wijzigen". De bouwstenen
+liggen klaar: App State **`gemeenteSelectNaam`** (persistent via secure
+storage, naast `gemeenteSelectId`, default `'28694'`) en
+`HeaderButtonsComponent`
+(`lib/components/header_buttons_component_widget.dart`) — een `Row` met
+op dit moment alleen een hamburger-knop (met `AlignedTooltip` "Menu
+openen") en een conditionele terugknop achter parameter
+`showBackButton`. Daar een `Text` bij die aan `gemeenteSelectNaam` hangt,
+met een On-Tap naar het keuzescherm. Let op de bekende valkuil: bij een
+geselecteerde **component-root** staat het **Component Name**-veld op
+vrijwel dezelfde plek als "Search properties...", dus verifieer vóór het
+typen welk widget geselecteerd is.
 
 **P2-20 · Eigenaar: Bob** (groter, eigen sessie; pas als het kleurbeeld
 klopt). Serif-koppen invoeren: de site zet elke inhoudelijke titel in