Claude/kind ptolemy 9 mvh0#42
Open
fpudelko wants to merge 318 commits into
Open
Conversation
- 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
- 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>
…ityka Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017dvpk3tbb8nsot5XNTaS8C
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No description provided.