Skip to content

Claude/kind ptolemy 9 mvh0#42

Open
fpudelko wants to merge 318 commits into
creativetimofficial:masterfrom
fpudelko:claude/kind-ptolemy-9Mvh0
Open

Claude/kind ptolemy 9 mvh0#42
fpudelko wants to merge 318 commits into
creativetimofficial:masterfrom
fpudelko:claude/kind-ptolemy-9Mvh0

Conversation

@fpudelko

@fpudelko fpudelko commented Jun 6, 2026

Copy link
Copy Markdown

No description provided.

claude added 30 commits June 4, 2026 00:32
- group_by_address: fields without a street number (e.g. address="Poznań")
  are each treated as singletons — no more 959-field group from one AI call;
  groups larger than 20 are split into chunks
- write_back_group: field_outreach upsert is now one batch POST for the whole
  group instead of N individual POSTs (N DB calls → 1)

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Default 65s gap between requests keeps token usage comfortably under
the 50k/min limit without needing reactive 429 retries.
Set --sleep 0 when using Sonnet or after a tier upgrade.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- city-only address (no street number) → skip, web search returns random results
- generic name (Boisko, Orlik, Hala sportowa...) + no website/phone → skip,
  these are school pitches with no online presence
Logs how many were skipped for transparency.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- enrich.py: add `description` field to RECORD_TOOL; save fill-if-empty to fields.description
- outreach.ts: add getOutreach(fieldId) for single-record lookup
- VenueDetailClient: fetch outreach data per venue; show AI-found booking URL as
  orange button (with provider name) when not already covered by internal booking;
  show ai_summary as subtle "Opis AI" block when set and meaningful (>40 chars);
  external booking section falls back to outreach.bookingUrl when field.bookingUrl is empty

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Reduces tokens per request from ~80k to ~40k, staying under the 50k TPM
Haiku limit with fewer 429s. Exposed as workflow input in enrich-venues.yml.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- group_by_address: fields without a street number (e.g. address="Poznań")
  are each treated as singletons — no more 959-field group from one AI call;
  groups larger than 20 are split into chunks
- write_back_group: field_outreach upsert is now one batch POST for the whole
  group instead of N individual POSTs (N DB calls → 1)

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Default 65s gap between requests keeps token usage comfortably under
the 50k/min limit without needing reactive 429 retries.
Set --sleep 0 when using Sonnet or after a tier upgrade.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- city-only address (no street number) → skip, web search returns random results
- generic name (Boisko, Orlik, Hala sportowa...) + no website/phone → skip,
  these are school pitches with no online presence
Logs how many were skipped for transparency.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
…currency to 2

Previously r.raise_for_status() crashed the whole run on the first 429.
Now retries up to 3x with exponential backoff matching enrich.py behavior.
Default concurrency lowered 4→2 to reduce rate limit pressure.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
…o all venues

Previously 7 venues sharing poznanazs.pl → 7 Claude calls for the same page.
Now: fetch once, write result to all field_outreach rows for that URL.
Also skips social media URLs (Facebook, Instagram etc.) which always return nieznany.
Logs show ×N count when multiple venues share a URL.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Proactive 65s sleep made 15 groups take 16 minutes. Reactive retry-after
handling already handles TPM rate limits correctly and only waits when needed.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
…change, filter count

- Kontakt column: phone/email/website shown as full text links, not just icons
- Expanded row: shows all venue data (operator, hours, description, full contact links)
- Przypisany: X button to release own assignment, Przejmij button to take from others,
  manual name input in expanded view (saves with notes)
- Filter count badge in filter bar: shows "N / total" when filters are active

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- Fix: release() passed undefined which saveOutreach silently skipped
  (condition !== undefined never triggered). Now uses null; saveOutreach
  checks with 'in patch' so null explicitly clears the DB column.
- Expanded row: header with venue name, full address, sport tags, and
  "Otwórz obiekt" button linking to /boisko/[id] in a new tab
- ExternalLink and MapPin icons added

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- Filter "Duplikaty kontaktu": shows only fields where phone or email appears
  in 3+ other records (likely bad AI enrichment); badge shows total count
- Warning badge in Kontakt column showing how many fields share this contact
- "Wyczyść dane kontaktowe" button in expanded row for suspicious records —
  clears phone/email/website from fields table with confirm dialog
- Updates local state immediately after clearing (no reload needed)

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
PostgREST caps unfiltered queries at 1000 rows. Add explicit limit(10000)
when no limit is specified so map/outreach views show all venues.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
The repo started from a "Material Kit React Native" template; the actual app
is entirely in frontend/ (Next.js). Removed dead template files never used by
the app:
- app.json, .watchmanconfig, babel.config.js (Expo/RN config)
- constants/, assets/ (RN Material Kit demo theme + icons)
- .DS_Store (also already in .gitignore)
- docker-compose.yml (referenced a non-existent ./backend + redis; broken)

Rewrote .env.example to match reality: removed dead vars (FastAPI API URL,
Redis, CORS), added the missing ANTHROPIC_API_KEY the scraper needs.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- Add validation.test.ts: 34 cases for sanitizeText (HTML/control-char
  stripping), validateName, sanitize{Description,Address}, validatePhone,
  normalizePhone — all run before every Supabase write.
- Fix events.test.ts: createEvent/joinEvent now call supabase.rpc(check_rate_limit)
  but the mock lacked .rpc, so 3 tests failed. Added the rpc mock plus a test
  for the rate-limit rejection path.

Suite: 38 passing (was 34 passing / 3 failing).

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- Add PRZEWODNIK.md: concise feature guide (user / manager / admin), the data
  pipeline, stack and local setup — meant to onboard a collaborator in ~5 min.
- Rewrite README: it described a fictional architecture (FastAPI backend, Redis,
  /games endpoints, Mapbox GL, a backend/ folder) that never existed. Now reflects
  reality: Next.js → Supabase direct, Leaflet, scraper in GitHub Actions.
- STATUS.md: mark removed RN/template dead code as done, link to the guide.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- Home page: remove description, chips and headline from USE_CASES cards.
  Keep only the eyebrow label (now text-lg bold) and the CTA link.
- Outreach panel: add compact inline badges (booking system, assignment,
  contact icon) inside the Obiekt cell on mobile (hidden on md+), so
  admins on phones see key info without expanding the row.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Keep eyebrow (larger, text-lg bold), description and feature chips.
Remove only the question-style headline text (e.g. "Masz wolny wieczór?").

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Replace flex-wrap horizontal layout with flex-col so phone, email,
website, operator and opening hours each get their own line and don't
overflow or get clipped on narrow screens.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- Extract shared EventCard (+ SPORT_EMOJI, isUpcoming) to
  components/EventCard.tsx; moje-gry now reuses it.
- New client HomeHero: logged-out/loading visitors see the full
  marketing hero; logged-in users get a compact welcome band plus
  their next 3 upcoming matches with a link to Moje gry.
- Add a "Jak to działa" section with 4 feature cards (mapa, zapisy
  przez link, dograj skład, bez instalacji) linking to the relevant
  pages — helps new visitors understand the product.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Migration 024: new map_visibility column ('public'|'organizer_only'),
default 'organizer_only'. Fields with any contact info auto-upgraded
to 'public'.

Frontend:
- Field type + FieldFilters gain mapVisibility field
- getFields() filters by map_visibility when provided
- Public map (LeafletMapImpl) fetches only 'public' fields
- Browse by sport (/boiska/[sport]) queries only 'public' fields
- Event creation pickers (VenuePicker, UnifiedLocationPicker) keep
  fetching all — organizers can pick any venue
- Sitemap only includes 'public' fields
- Admin outreach panel: visibility toggle button (Eye/EyeOff) in
  expanded row header; collapsed row shows EyeOff icon for hidden
  venues so admin sees at a glance what's not on the map

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
- MapVisibility type gains 'hidden' (field excluded from everything,
  including organizer pickers — for junk AI entries)
- Migration 024 CHECK updated to allow 'public'|'organizer_only'|'hidden'
- VenuePicker + UnifiedLocationPicker filter out hidden fields client-side
- Outreach panel: replace 2-state toggle with inline 3-button segmented
  control (Na mapie / Tylko org. / Ukryty); collapsed row shows EyeOff
  for organizer_only, red X for hidden

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Mobile hamburger menu:
- Replace flat nav list with 2 large CTA buttons at top:
  amber "Znajdź grę" + primary "Stwórz mecz"
- Secondary "Odkryj" section: Stałe gierki, Mapa boisk (with icons)
- "Twoje" section for logged-in users; "Admin" section for admins
- Section labels make organizer vs player roles immediately clear

Homepage intent cards:
- Split 4 cards into 2 labelled columns: "Organizujesz?" (green)
  and "Grasz?" (amber) — each with 2 cards underneath
- Reorder: organizer pair (Organizuję + Gramy co tydzień) left,
  player pair (Szukam gry + Dograj skład) right

Sports section:
- Merge futsal into Piłka nożna — counts combined, one chip shown

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
The feature tiles (Mapa boisk, Zapisy przez link, Dograj skład,
Bez instalacji) repeated CTAs already covered by the intent cards
above. Swap them for a prose-led "how it works" section:

- Punchy heading + relatable intro (WhatsApp/excel pain point)
- Three numbered steps: znajdź/stwórz → rozdaj link → zagraj
- Closing line covers the "no install, Google login" value prop

Removes FEATURES array, Feature type, and now-unused icon imports.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Database (migration 025):
- game_alerts: user alert preferences (sport, days, location radius)
- notifications: in-app inbox with realtime support
- haversine_km() SQL helper for distance queries
- count_alert_seekers() RPC — how many users want a game here
- get_nearby_events() RPC — public events within N km

Backend:
- notify-game-alert edge function: on public event creation,
  finds matching alerts, inserts notifications, sends emails via Resend

Frontend:
- lib/alerts.ts — CRUD for alerts, geocoding via Nominatim
- lib/notifications.ts — read + markRead helpers
- events.ts — getNearbyEvents(); fire notify-game-alert on
  createEvent() and setVisibility() → public
- NotificationBell — realtime bell with unread badge + dropdown
  (added to desktop header)
- AlertSetupDialog — sport/days/location/radius picker with GPS
  + Nominatim geocoding fallback; one alert per user (upsert)
- NearbyGames — opt-in location widget: shows nearby public events
  + alert setup CTA; persists location in localStorage
- HomeHero (PersonalizedHero) — NearbyGames below personal games
- /wydarzenia/nowe — seeker count nudge above visibility picker
  ("X osób szuka gry w rejonie — rozważ otwarcie zapisów")

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Geolocation:
- New lib/geo.ts: shared getCurrentLocation() with specific error
  messages (denied/insecure/unavailable/timeout), longer 15s timeout,
  cached-position reuse, secure-context check
- Wire it into wydarzenia page, NearbyGames, AlertSetupDialog —
  failures now explain the cause and fall back to manual city entry

Znajdź grę page:
- Remove "Wszystkie / Moje" tabs — always shows public events
  (own events are reachable elsewhere; less clutter)
- Sport filter chips are now emoji-only squares (no labels) so they
  fit without horizontal scrolling on mobile; aria-label/title for a11y

Landing page order (logged-out flow):
- Move "Jak to działa" up to right after the hero (explain first)
- Move map preview down below the intent cards (explore later)
- New flow: Hero → How it works → Intent cards → Map → Sports → CTA

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Fields with map_visibility='organizer_only' now appear on the map —
only 'hidden' (junk/spam entries) are excluded. Previously the map
filtered for 'public' only, hiding valid venues that hadn't been
contacted yet.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
Replaces the auto-screenshot (which showed the map) with a generated
1200×630 card: dark green gradient, Bojo logo, headline, amber CTA
pills, and faint sport emoji decoration. Generated via next/og
ImageResponse so no static file needed.

https://claude.ai/code/session_01JtBCwZZN3iYYf2rfG8kGvv
claude added 30 commits June 16, 2026 10:27
- Hero: "Znajdź mecz. Albo stwórz własny." replaces "Czas na bojo?"
- UnassignedTray: swipe right → Niebiescy, swipe left → Czerwoni
  with live background reveal and 72px commit threshold; N/C buttons
  remain for tap access
- Publish toggle onClick is now explicit ?.() to avoid undefined call

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
Adds .select('id') after update so 0-row updates (silent RLS block)
throw a descriptive error instead of silently doing nothing.

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
Direct PATCH /rest/v1/events was returning 400 for teams_published.
Created SQL function set_event_teams_published() (SECURITY INVOKER)
and switched publishTeams/unpublishTeams to use supabase.rpc().

REQUIRES: run supabase/migrations/042_publish_teams_fn.sql in Supabase.

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
Player profiles
- /gracz/[id] now shows real aggregated stats (across ALL events, via new
  get_player_stats RPC) plus a clickable game-history list — previously it
  read player_stats which is only populated for recurring groups, so most
  users saw zeros.
- Participant names/avatars in the event page now link to /gracz/[id]
  (guests stay non-clickable — they have no profile).

Groups
- New tables groups + group_members, events.group_id, RLS, and a trigger
  that auto-adds the creator as admin (migration 044).
- lib/groups.ts: create, list mine, get, join by code, leave, remove member,
  delete, group events.
- Pages: /grupy (list + join-by-code), /grupy/nowe (create), /grupy/[id]
  (members, events, invite code, create match in group, leave/delete).
- "Grupy" nav link (desktop + mobile).
- New-event form accepts ?group= to attach an event to a group and prefills
  the sport; event detail page shows a group chip linking back.

REQUIRES running in Supabase SQL editor (in order):
  043_player_stats_fn.sql
  044_groups.sql

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
- Tailwind darkMode: 'class', CSS vars for ink/canvas
- ThemeProvider (next-themes) with system preference default
- Sun/moon toggle in Header (desktop + mobile, always visible)
- Button, EventBrowseCard dark variants
- Past events: show 'Rozegrany/Anulowany' badge, hide 'Dołącz', dim card
- Player stats: remove 'mecze dołączone', add smart absence box
  (0 no-shows → 'Bezbłędna' green badge; else count with warning color)

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
- grupy/[id]: 'Zaproś do grupy' section moved below Members card,
  redesigned — code large on left, copy + share buttons on right
- HomeHero: MyGroupsSection shows user's groups above 'Jak to działa'
  (logged-in only, max 2, links to /grupy)

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
… in profil

- gracz/[id]: remove frekwencja % bar entirely
- reliablePlayer now = eventsJoined >= 5 AND noShows === 0 (consistent with badge)
- profil: 'Moje statystyki' button top-right → /gracz/[userId]

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
New joiners after teams are published now appear in an 'Nieprzypisani'
section below the two team columns, both in ParticipantsList (public view)
and in the existing UnassignedTray in TeamsPanel (organizer view).

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
ParticipantsList now always shows flat lists (zapisani + rezerwowi).
Teams are a separate PublishedTeamsCard below, shown when teamsPublished.
Organiser still gets the full TeamsPanel (with drag/assign) separately.

https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
Replace generic slate dark mode with a refined ink-blue palette (#0D1117
canvas, #161C27 cards, #1C2333 raised surfaces) that complements the
forest-green brand. Add global CSS elevation overrides so bg-white/
bg-slate-800/bg-slate-900 Tailwind classes automatically use the new
surface tokens without touching every component. Add near-invisible
hairline borders (rgba(255,255,255,0.07)) instead of heavy slate-700
lines. Fix Header desktop/mobile nav: add dark variants for active and
hover states, darken backdrop-blur bar and mobile overlay to canvas
color, add dark styles to AdminMenu dropdown. Add surface/surface-raised/
surface-overlay tokens to Tailwind config for future component use.

Co-Authored-By: Claude <noreply@anthropic.com>
- Wrap organizer card in px-4 so it aligns with other event detail cards
  instead of going full-bleed edge-to-edge
- Add py-1 to sport/date filter chip rows so ring shadows are not clipped
  by the overflow-x:auto scroll container boundary
- Fix section labels: 'BOISKO' and 'ORGANIZATOR' changed from text-slate-400
  to text-slate-500 (contrast ratio 4.6:1 vs 2.7:1 on white)
- Fix inverted dark mode text: text-slate-400 dark:text-slate-500 was making
  muted text MORE muted in dark mode; corrected to text-slate-500 dark:text-slate-400
  across wydarzenia, grupy, gracz, moje-gry, EventBrowseCard

Co-Authored-By: Claude <noreply@anthropic.com>
The app was written mostly with plain light utilities (bg-white,
text-slate-700, border-slate-100…) with no dark: variant, so most cards
stayed white in dark mode. Instead of editing 40+ files inconsistently,
remap the common neutral utilities under .dark in globals.css so the
entire app turns dark cohesively:

- bg-white → surface, bg-slate-50 → canvas (page/recessed),
  bg-slate-100 → raised (chips/tracks), bg-slate-200 → overlay
- full text-slate-300..900 scale → warm off-white ramp
- border/ring/divide slate-100..300 → hairlines
- hover:bg-slate-50/100 → subtle white overlays
- brand green text-primary-600..800 lifted to a lighter green so it
  reads on dark surfaces; bg-primary-50 → translucent green tint

Author-provided dark:* variants are re-asserted afterwards so explicit
opt-ins always win over these defaults.

Co-Authored-By: Claude <noreply@anthropic.com>
Re-adds the JoinCodePanel below the organiser card for all non-cancelled
events that have a joinCode. The panel shows "Zaproś znajomych" with
Share and Copy buttons but no raw code string, matching the original UX
before the join-code feature was added.

Co-Authored-By: Claude <noreply@anthropic.com>
Skip button on each card hides the venue from the active review queue
without making a permanent decision. Skipped venues collect in a
collapsible 'Pominięte' section below the main grid, shown with an amber
'← Pominięty' badge and amber card border so they're easy to spot when
you return to them. No merge/deduplication UI.

Session progress bar tracks how many pending venues have been actioned
(Zatwierdź or Ukryj) vs. the total that were pending when the page
loaded, showing 'X / Y przetworzonych' and a green fill bar. Skip
count shown inline in amber next to the progress. Result count (active
count / total) shown next to the filter row.

Co-Authored-By: Claude <noreply@anthropic.com>
matchesPlayed now counts non-reserve participations in non-cancelled
events that have already started (event_date + event_time <= now()),
replacing the old logic that required a match_result record. Migration
045 updates the get_player_stats RPC.

Organizer card at the bottom of the event page is now a Link to
/gracz/[organizerId] with a ChevronRight arrow, matching the style of
other clickable player rows. Falls back to a plain div when
organizerId is missing.

Co-Authored-By: Claude <noreply@anthropic.com>
New CoverUpload component uploads to the 'covers' storage bucket and
shows a camera button overlaid on the hero area. Group and event owners
can add, change, or remove a cover image directly from the detail pages.
Migration 046 adds cover_image_url columns to both tables.

Co-Authored-By: Claude <noreply@anthropic.com>
Track app-wide user actions (login, event created/joined, group
created/joined) in a new analytics_events table and surface them to
admins at /admin/analityka. The dashboard shows active users (1/7/30d),
a retention metric (users returning on another day), action counts, a
14-day activity chart, and a raw event log.

- migration 047: analytics_events table, RLS (authenticated insert,
  admin-only read)
- lib/analytics.ts: fire-and-forget track() helper
- login tracked centrally in AuthProvider (deduped per browser session)
- createEvent/joinEvent/createGroup/joinGroup instrumented

Co-Authored-By: Claude <noreply@anthropic.com>
Add /g/[code] short link that opens the group with a join action
(mirrors /d/[code] for events). The invite section now leads with
"Udostępnij link" / "Kopiuj link" (equal-width, wrapping buttons so
they never overflow the card) and keeps the join code as a fallback.

Co-Authored-By: Claude <noreply@anthropic.com>
Bring back the organizer option "require approval to join" with full
enforcement that was lost in the join-code migration:

- migration 048: pending_approval column on event_participants
- form: "Wymagaj akceptacji" toggle on create + edit pages
- joinEvent: when approval is required, joins land as pending requests
  (excluded from roster, capacity, and card counts)
- organizer: "Prośby o dołączenie" queue with approve/reject
- joiner: "Oczekujesz na akceptację" banner with cancel
- approveParticipant decides reserve vs. regular by free capacity at
  approval time; rejectParticipant removes the request

Co-Authored-By: Claude <noreply@anthropic.com>
getGroupEvents/getGroupMembers failures previously fell into the same
catch as a missing group, rendering "Nie znaleziono grupy" for a group
that exists. Now only a null/unreadable group triggers not-found; the
header renders even if events/members fail to load.

Co-Authored-By: Claude <noreply@anthropic.com>
Group creators get an "Edytuj" button on the group page leading to
/grupy/[id]/edytuj (name, sport, city, description). The page gates on
ownership (createdBy === user) and updateGroup is further protected by
the existing "Creator updates group" RLS policy.

Co-Authored-By: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants