Napraw crash strony wypadu: hook useMemo wołany warunkowo po wczesnym return - #19
Open
uncleeai wants to merge 292 commits into
Open
Napraw crash strony wypadu: hook useMemo wołany warunkowo po wczesnym return#19uncleeai wants to merge 292 commits into
uncleeai wants to merge 292 commits into
Conversation
- Włącz experimental.viewTransition w next.config - Komponent PageTransition owijający treść strony w React <ViewTransition> z mapą enter/exit dla typów nav-forward / nav-back - transitionTypes na linkach: wejście w wypad = nav-forward (slide z prawej), powrót do listy = nav-back (slide z lewej); router.push też taguje forward - CSS kierunkowych slajdów + poszanowanie prefers-reduced-motion - Typy canary Reacta (ViewTransition) włączone przez referencję w d.ts Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
…integracji React - Provider w layoucie (nie odmontowuje się przy nawigacji) owija router.push w document.startViewTransition i kończy animację po zmianie pathname - Kierunek przez data-vt na <html>; CSS animuje ::view-transition-old/new(root) (forward: nowa z prawej na wierzchu; back: bieżąca zjeżdża w prawo, odsłania poprzednią) - Linki/karty wołają useTransitionNavigate (z obsługą Cmd/Ctrl-klik i reduced-motion) - Usunięto experimental.viewTransition, komponent PageTransition i typy canary Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
- GlassBackground: natywna pętla zamiast per-klatkowego seeku currentTime (najcięższy koszt na iOS); pauza wideo gdy karta w tle - Hoist tła do layoutu — jedna persystentna instancja zamiast remountu i reloadu wideo przy każdej nawigacji (płynniejsze też przejścia stron) - Wideo na własnej warstwie GPU (translateZ/will-change), blur 60px -> 44px (wygląd ~ten sam, taniej) - contain: layout paint na .form-collapse — animacja rozwijania nie reflowuje całej listy wypadów Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
…nelem Po przeniesieniu tła do body z-index:-1 chował je za nieprzezroczystym tłem body (aurora widoczna tylko poniżej treści). Tło na z-index:0, .container z treścią na z-index:1 — aurora wypełnia ekran za zawartością jak wcześniej. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
Rozmycie szkła nad odtwarzanym wideo przelicza się co klatkę dla każdej karty. Karty listy (.event-rich) powtarzają się N razy — zamiast frostowania dają teraz ciemne półprzezroczyste tło (panel). Duże karty i CTA zostają szklane. Duży zysk na głównym ekranie przy minimalnej zmianie wyglądu. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
- BackgroundProvider (kontekst + zapamiętywanie w localStorage) - GlassBackground respektuje preferencję (wyłączone = brak wideo, czarne tło) - Przełącznik (iOS-owy switch) w modalu profilu, pod wyborem awatara - Wyłączenie tła wyraźnie odciąża słabsze urządzenia (brak dekodu/blura wideo, szkło nad statycznym tłem liczone raz zamiast co klatkę) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
Duża karta formularza nowego wypadu rozmywała tło co klatkę w trakcie animacji wysokości — zamiana na ciemny kryjący panel (jak karty listy) usuwa ten koszt. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
To była percepcja, nie realny lag — wracamy do backdrop-filter. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
- Lekki singleton w pamięci (dataCache): dashboard zapisuje komplet danych, strona wypadu seeduje z niego stan na start -> brak 'Wczytuję...' przy wejściu z listy - Strona wypadu po świeżym load() odświeża cache (mergeEventData), więc powrót na listę pokazuje aktualne dane - Dashboard też seeduje z cache (instant powrót) - Realtime i load() bez zmian — głosy/zmiany dalej aktualizują się na żywo; cache to tylko zaliczka na render, nie zamiennik pobierania Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
Rozwiązuje przeskok na pętli i okazjonalny freeze wideo. Dwie warstwy radialnych gradientów animowane tylko transformem (GPU), pętla alternate = bezszwowa. Koszt znikomy, brak dekodowania wideo. Przełącznik tła w profilu działa jak dotąd. Plik wideo zostaje w repo na wypadek powrotu. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
Zamiast reagować na mysz (--posX/--posY z pointermove), zmienne są rejestrowane przez @Property i animowane dwiema pętlami alternate o różnych czasach — organiczny, bezszwowy ruch tła. Nadal tani (brak wideo), przełącznik tła działa. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
- filter: blur(80px) + opacity 0.45 (więcej rozmycia, ciemniej) - gradient statyczny, ruch przez translate/scale (GPU) — taniej niż animowanie pozycji gradientu; blur liczony raz - mocny blur maskuje 'ściśnięcie' radialnych gradientów na wąskim ekranie telefonu Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2
- Usuń nagłówek „Planner" i tekst pod nim z ekranu logowania. - Wyśrodkuj box logowania w pionie (.auth-screen), karta w szklanym stylu (klasa glass-page) — spójnie z resztą apki. Przycisk gościa zostaje. - Przywróć tło wideo, ale wyłącznie na ekranie logowania (LoginBackground): jedna karta, krótka chwila. Globalna aurora chowana klasą body.login-active, żeby nie liczyć dwóch teł naraz; reszta apki bez zmian. Szanuje przełącznik tła i pauzuje wideo, gdy karta jest w tle. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Slide na ciężkich szklanych ekranach zacinał pierwszą klatkę, a próby wyłączenia tła ze snapshotu odbijały się czernią/przeskokiem po zescrollowaniu (ograniczenie WebKita dla nazwanych elementów fixed). Crossfade nie przesuwa nic, więc jest odporny na scroll i bez artefaktów pozycyjnych, a ewentualny koszt snapshotu jest niewidoczny. Nowa strona pojawia się nad starą (opacity), strony są nieprzezroczyste więc przenikanie jest czyste. Slide'owe keyframes i zależność od kierunku usunięte. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Crossfade/slide oparte na View Transitions snapshotowały całą stronę, a snapshot przewiniętej strony kotwiczy się do góry dokumentu → na starcie przejścia treść skakała (stutter widoczny po zescrollowaniu). Problem jest wpisany w snapshoty, niezależnie od typu animacji. Rezygnujemy ze snapshotów: nawigacja to zwykły router.push, a nowa strona animuje się przy zamontowaniu (keyframes page-in: lekki unos + pojawienie) czystym transform/opacity na <main>. Brak kosztu rasteryzacji, odporne na scroll, bez przeskoku. Tracimy zanikanie starej strony, ale to stabilny, standardowy wzorzec. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Przy otwieraniu wypadu z przewiniętej listy strona wypadu montowała się, gdy okno było jeszcze przewinięte w dół: przez klatkę widać było pustkę poniżej treści, a na iOS fixed tło potrafiło mignąć na czarno przy reflowie, po czym scroll przeskakiwał na górę. Otwierane z góry (scroll 0) — bez problemu. navigate() resetuje teraz scroll do zera synchronicznie tuż przed router.push, więc strona wypadu montuje się już przy scroll 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Diagnoza: kod animacji nie zmienił się od 9ce501e — slide stutter/mignięcie czerni brały się z TŁA. Po 9ce501e wideo zamieniono na ciężką aurorę CSS (blur 80px + background-blend-mode), która jest droga w przemalowaniu: każda nawigacja/reset scrolla wymuszały reflow → zacięcie, a fixed element na klatkę nie zdążył się namalować → czerń. Wideo jako warstwa GPU przemalowuje się tanio. - transition.tsx + GlassBackground.tsx przywrócone 1:1 z 9ce501e (slide VT + wideo). - globals.css: .glass-aurora (+ keyframes aurora-*) → .glass-video; animacja wejścia (page-in) → slide ::view-transition (root) z data-vt. - Reset scrolla przed nawigacją zniknął wraz z powrotem do oryginalnego transition.tsx. - Przeprojektowany ekran logowania zostaje bez zmian. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Push (działa też przy zamkniętej apce; iOS tylko w PWA na ekranie głównym, 16.4+): - public/sw.js — service worker (push + notificationclick → otwiera wypad). - src/lib/push.ts — subskrypcja: rejestracja SW, zgoda, zapis do bazy; wsparcie i wykrycie standalone. - ProfileMenu — przełącznik „Powiadomienia" (gdy wspierane); poza standalone na iOS podpowiedź „dodaj do ekranu głównego". - supabase/schema.sql — tabela push_subscriptions + RLS (każdy zarządza swoimi). - supabase/functions/notify-new-event — Edge Function (Deno/web-push) wyzwalana Database Webhookiem na INSERT events; rozsyła push do wszystkich poza twórcą, sprząta martwe subskrypcje (404/410); chroniona nagłówkiem x-webhook-secret. Toast na żywo (gdy apka otwarta), bez żadnej konfiguracji: - NewEventToast w auth.tsx — Realtime na INSERT events, pływający baner + link. Dodatkowo: NEXT_PUBLIC_VAPID_PUBLIC_KEY w .env.example, instrukcja konfiguracji w README, tsconfig wyklucza supabase/functions z type-checku Next, aktualizacja CLAUDE.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Toast łapał tylko pierwszy wypad po otwarciu apki: dashboard i toast miały DWA osobne kanały Realtime na tej samej tabeli `events`, a Supabase dostarczał zmiany tylko jednemu. Teraz jest jedna globalna subskrypcja `events` (w NewEventToast), która rozgłasza zmiany zdarzeniem `planner:events-changed`; dashboard słucha go zamiast subskrybować `events` osobno. Dodatkowo: - Edge Function: czysta wersja bez logów debug; sekret webhooka opcjonalny i sprawdzany z query param `?key=` lub nagłówka (query param jest niezawodny). - README: pomysły na dalej — opis wyjazdu (kolumna description już jest), przypomnienia (pg_cron + push) i ping od organizatora do niegłosujących. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Opis wypadu: - pole „Opis (opcjonalnie)" (textarea) w obu wariantach formularza „Nowy wypad", zapisywane do istniejącej kolumny events.description, - wyświetlanie opisu na stronie wypadu (.event-description, zachowuje przełamania). Ustawienia: - nowy komponent SettingsMenu (ikona koła zębatego, IconGear) renderowany obok avatara; przeniesione tam przełączniki „Animowane tło" i „Powiadomienia", - ProfileMenu odchudzone — zostaje awatar/zdjęcie, nick i wylogowanie. Style: textarea (spójny z inputami) i .settings-trigger. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Edge Function notify-reminders odpalana przez pg_cron: dla wypadów starszych niż 24h, z terminem w przyszłości i bez znacznika reminded_at, wysyła push do osób z paczki, które nie oddały głosu (poza twórcą), po czym ustawia events.reminded_at — jedno przypomnienie na wypad. Filtr 24h–14 dni, żeby nie zalewać starymi. - schema.sql: kolumna events.reminded_at. - supabase/functions/notify-reminders: logika + opcjonalny sekret przez ?key=. - README: instrukcja (deploy, pg_cron + pg_net, harmonogram cron.schedule). - CLAUDE.md: struktura + model danych. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Po wyczyszczeniu bazy lub zalogowaniu na inne konto przeglądarka miała subskrypcję, ale w `push_subscriptions` brakowało wiersza (albo był na starym koncie) — push przestawał przychodzić do czasu ręcznego przeklikania OFF/ON. resyncPushSubscription(userId) po cichu upsertuje istniejącą subskrypcję przeglądarki pod aktualne konto; wołane w AuthProvider przy każdym zalogowaniu. Nie prosi o zgodę ani nie rejestruje SW — działa tylko gdy subskrypcja już istnieje. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Bug: na części iOS (też topowych) tap w event czasem nie przenosił na stronę wypadu — zostawał na home, a slajd leciał „home → home". Przyczyna: router.push odpalany WEWNĄTRZ startViewTransition bywał porzucany przez Safari (callback przejścia jest traktowany specjalnie), więc nawigacja nie dochodziła. Rezygnujemy ze snapshotów: navigate() to zwykły router.push (nigdy nie zawiedzie), a nowa strona animuje się przy zamontowaniu (keyframes page-in: lekki unos + fade) na <main>. Bulletproof na każdym urządzeniu, brak wyścigów. Tracimy wyjazd starej strony — świadomy kompromis na rzecz niezawodności. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
page-in: translateX(48px)→0 + fade, 0.32s (zamiast 12px w górę) — bliżej dawnego iOS-owego slajdu, ale nadal czysty CSS bez snapshotów. .container przycina poziom, żeby wjazd nie dawał bocznego scrolla (brak position: sticky, więc bezpieczne). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
hidden + overflow-y: visible wymusza wg spec CSS overflow-y: auto → .container stawał się pionowym kontenerem scrolla (footgun przy przewijaniu/scroll-into-view). clip przycina poziom przy wjeździe strony, nie tworząc scrollera. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
…(A+B) Rozdzielenie „prowadzącego terminu" (na żywo, informacyjnie) od „ustalonego" (wypad zdecydowany → trafia do Nadchodzące). Jeden głos „Wchodzę" już NIE przenosi wypadu z „W trakcie" ani nie chowa paska frekwencji. getEventStatus (types.ts): ustalone gdy A) organizator zapisał confirmed_slot_id (pierwszeństwo, sticky), albo B) WSZYSCY z paczki (łącznie z organizatorem) dali znać i jest prowadzący termin. Liczone na żywo → zmiana głosu przestawia/cofa automat; ręczne trzyma się do odznaczenia. Dashboard: sekcje open/upcoming/past wg getEventStatus; pasek „% dało znać" zostaje w „W trakcie" + podpowiedź prowadzącego terminu („… prowadzi · N wchodzi"). Strona wypadu: przyciski organizatora „Ustal ten termin"/„Odznacz", odznaka „✓ Ustalony", nagłówek rozróżnia „Ustalone" (auto/ręczne) od „Na czele". Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
- „Minione" → „Odbyte" (ustalone, po dacie; plakietka „✓ Odbyte"). - Nowa sekcja „Nie ustalono": wypady nieustalone, których WSZYSTKIE terminy minęły. Pokazywane do 24h po ostatnim terminie (plakietka „Nie ustalono", „Termin minął"), potem znikają z listy (archiwizacja klient-side — rekord zostaje w bazie, nic nie kasujemy; z bezpośredniego linku dalej się otwiera). - „W trakcie" trzyma tylko wypady z przyszłym terminem lub bez terminów. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014qgyoEJ3gYpX2gADzgM7JZ
Poprzednia wersja zwijała godzinę, a data WSKAKIWAŁA w jej miejsce (pola dosunięte do prawej). Teraz blok pól Start ma stałą szerokość (216px), a animują się proporcje kolumn grida (1fr:0.58fr ↔ 1fr:0fr) + gap — pill daty rośnie i pochłania miejsce po godzinie jak jeden przelewający się kształt. Wiersz Koniec zostaje na flexie (jeden pill). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
Bazowe .slot-range .dt-field { width: auto } + input { width: 100% } robiło
błędne koło (wrapper od contentu, input od wrappera → pill zapadał się do
zera, „Godz." wystawało poza tło). W siatce dual wrapper wypełnia komórkę.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
…wkt1r Własne tło wypadu: upload, pinch-to-crop i edycja przez kreator
Fundament galerii zdjęć wypadu (pliki w Cloudflare R2, metadane w Supabase): - tabela event_photos (wypad, autor, ścieżki podgląd/oryginał, taken_at) z RLS jak komentarze (czyta paczka, wstawia autor, kasuje autor/organizator/ admin) + realtime; zaaplikowana na produkcję, - Edge Function sign-photo-upload (verify JWT): presigned PUT do R2 przez aws4fetch, ścieżki budowane server-side, URL-e ważne 10 min; wdrożona, - lib/gallery.ts: podgląd JPEG 2048px na telefonie, upload par (oryginał bajt-w-bajt + podgląd), wpisy metadanych, flaga isGalleryConfigured (czeka na publiczny URL bucketa). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
- EventGallery na stronie wypadu: sekcja „Zdjęcia" z siatką 3×N podglądów (lazy), kafelek „+" z multi-selectem i postępem x/y, realtime (zdjęcia wskakują u wszystkich na żywo). - Pełnoekranowy viewer: swipe między zdjęciami, licznik, podpis (autor + data zrobienia), „Oryginał" (pobranie bajt-w-bajt z R2) i „Usuń" (autor/organizator/admin, RLS pilnuje po stronie bazy). - Dashboard: odbyte wypady z fotkami dostają znaczek „📸 N" obok GG. - R2_PUBLIC_BASE ustawione na publiczny URL bucketa (r2.dev — dla skali paczki w zupełności; upgrade = własna domena i jedna stała). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
Debug „nie udało się wysłać przesyłki": w logach Supabase ZERO requestów do sign-photo-upload (bezpośredni test funkcji: OPTIONS 200 + 401 bez tokena, oba logowane) — błąd działał się w przeglądarce przed siecią, najpewniej przy generowaniu podglądu. Rura przezbrojona per plik: podgląd z fallbackiem (padnie → oryginał robi za preview), podpis + PUT-y + wiersz metadanych osobno dla każdego zdjęcia (częściowy sukces zostaje), komunikaty błędów wskazują etap (podpis / R2 / metadane) i lecą też do console.error. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
functions.invoke zawijał prawdziwą przyczynę w „Failed to send a request to the Edge Function". Goły fetch na /functions/v1/sign-photo-upload z jawnym tokenem sesji i anon key: odrzucenie sieci, status HTTP i treść błędu lecą wprost do alertu (i konsoli) — wiadomo, gdzie boli. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
„Load failed" na iPhonie przy działającym REST na tej samej domenie = podpis content blockera Safari; ścieżka zawierała „photo-upload" — wzorzec z list filtrów. Ta sama funkcja pod neutralnym slugiem (wdrożona), klient przepięty, stara nazwa usunięta z repo. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
Backend zweryfikowany end-to-end prawdziwym tokenem użytkownika (sign → presigned PUT do R2 → odczyt z pub URL, wszystko 200) — klucze R2 i funkcja są dobre; pada wyłącznie fetch ze strony na preview. Klient próbuje teraz dwóch adresów tej samej funkcji (główny + <ref>.functions.supabase.co) — inna nazwa hosta omija filtry URL; błąd raportuje oba. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XQ2tgPgR9wwC3eT5DbjfC7
Photo upload failed on iOS Safari with "Podpis wysyłki: sieć odrzuciła żądanie (Load failed)" — a network-level fetch TypeError. Verified the gallery-sign Edge Function is healthy (curl: OPTIONS 200 + CORS, invalid JWT → readable 401 + CORS; a valid signed upload appears in the function logs), yet real user attempts never reach Supabase at all. The browser's cross-origin call straight to *.supabase.co dies before leaving the device — the class of failure content blockers, iCloud Private Relay and iOS preflight quirks cause for third-party requests. Earlier client-side workarounds (function rename, legacy-domain fallback) couldn't fix that. Add a Next.js API route that the browser calls same-origin (/api/gallery-sign) and which forwards server-side to the Edge Function. Same-origin means no CORS/preflight and no third-party request, so blockers have nothing to match; the server→Supabase hop is as reliable as curl. The user's JWT is forwarded, so the function still verifies identity and R2 keys stay in Supabase. Client drops the fragile dual-domain fetch for a single call to the proxy. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
When the Edge Function gateway returns 404 ("Requested function was not
found"), the real cause is almost always that NEXT_PUBLIC_SUPABASE_URL
points at a project where gallery-sign isn't deployed. Replace the raw
gateway JSON with a clear message naming the project ref we forwarded to,
so the mismatch is obvious from the app itself.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Uploads run sequentially, so picking too many photos meant waiting them all out. Thread an AbortSignal through uploadEventPhotos: it's checked before each file and passed to the sign/R2 fetches. While uploading, the "+" tile becomes a stop button (accent square + done/total) that aborts on tap — already-uploaded photos stay, the rest never start, and a user-initiated stop is not reported as an error. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
…le style Viewer now pages between photos with a finger-tracking swipe: a 3-slide track (prev/current/next) follows the drag and, on release, eases to the neighbor past a threshold (or springs back) with a cubic-bezier transition, then commits the index seamlessly. Edge resistance when there's no neighbor. Also revert the upload stop-tile to its previous look — plain mono "done/total…" text instead of the accent square; it stays tappable to abort. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Deleting a photo now soft-deletes (sets event_photos.deleted_at): it vanishes from the gallery immediately but the R2 files stay as a buffer. A new pg_cron Edge Function gallery-gc runs daily and, past a 30-day TTL, deletes both R2 objects (idempotent S3 DELETE via aws4fetch) and then the row — a failed R2 delete keeps the row for the next run, so no orphans. - schema: event_photos.deleted_at column, partial trash index, UPDATE policy (same actors as delete) for the soft delete. - client: deleteEventPhoto UPDATEs deleted_at; gallery load and dashboard photo counts filter deleted_at is null. - function: gallery-gc (no-verify-jwt, optional WEBHOOK_SECRET), service role, batch of 200, only purges rows whose R2 objects are confirmed gone. Applied to the live project: migration, function deploy, daily cron, and an end-to-end purge test (R2 token confirmed to allow DELETE). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
The fixed-duration paging felt like a hard jump on release. Two fixes: - Keep the transform transition always defined and drive it via transitionDuration (0ms while tracking the finger, >0 on release). Toggling the transition on in the same frame as the value change made Safari skip the animation, snapping instead of easing. - Compute the settle duration from the gesture's release velocity so a flick continues fast and a slow drag eases gently (iOS-like momentum), with a soft deceleration curve. Fast flicks also commit below the distance threshold. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Drop the 6px slide padding that showed a black seam between photos while swiping. Settle animation reworked for smoothness: ease-out curve with a long deceleration tail (0.16, 1, 0.3, 1) and longer velocity-scaled durations (300-560ms), so release starts at finger speed and glides to a stop instead of snapping. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
CSS-transition paging can't be grabbed mid-flight, which forced blocking new swipes until the animation ended and never felt native. Replace it with a rAF-driven critically damped spring: position/velocity live in refs, the transform is written directly to the element (no per-frame setState), and the settle starts from the smoothed finger velocity, so release glides in the gesture's own tempo. A new touch cancels the spring and picks the photo up exactly where it is. Index commits immediately on release with a width-compensated position (same visual frame), so there is no pending state and rapid successive flicks chain naturally. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Slides were keyed by slot (0/1/2), so the immediate index commit on release mutated src on both visible <img> elements mid-animation, forcing re-decode right when the spring runs — dropped frames. Key slides by photo index instead: React moves the already-decoded elements and only mounts the one new offscreen neighbor, decoded off the main thread (decoding="async"). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Restore the seam between photos, but as a flex gap on the track instead of slide padding — the current photo still reaches the screen edges and the 16px gap is only visible during the transition, like iOS Photos. The page stride and centering account for the gap (width + gap). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Swipe a comment row left to reveal a red "Usuń" button (76px, with rubber-band resistance past it and a haptic tick on open); tap it to run the existing confirm+delete flow. Content tracks the finger via direct style writes, release settles through the CSS transition, and React state pins the open/closed result. Horizontal-vs-vertical intent detection keeps page scroll working (touch-action: pan-y), and a >12px horizontal move already cancels the reaction long-press, so gestures don't collide. Available to the same people as delete (author/organizer/admin); opening another row or tapping the content closes the revealed button. On coarse pointers the header ✕ is hidden — swipe replaces it; desktop keeps ✕. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Swiping a just-posted comment during the optimistic window grabbed a row that was about to be replaced by its saved version (new id, new DOM node), causing a visual jump. Gate the swipe handlers on `saved`, mirroring the reaction long-press, so the gesture only binds to a stable element. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
The event page showed no photo at all: the hero image logic (custom image_url with focus, or the curated category photo from the emoji plus its hero_crops framing) only existed in the dashboard cards, so opening an event dropped all of its visual identity. Past events were worse: a dashed "already happened" banner dominated, the full ready check listed stale proposals, and the photos - the reason you revisit - sat at the bottom. - New EventHero component renders the photo full-bleed at the top (bleeding past the container padding and safe areas), with floating back/edit buttons over it and the title plus meta below, left-aligned. Reuses the dashboard's photo layers and framing. Events without a photo or a mapped emoji fall back to the previous text header rather than an empty frame. - Past events: gallery moves directly under the hero (unlabelled, the grid speaks for itself), the ready check collapses to the settled slot alone under a "Kto był" label, and the banner becomes a quiet "Odbyło się" chip next to the meta. The calendar chip hides once the event is over. - Gallery gained an empty state, which it never had; it would have read as broken now that it leads the past-event screen. Two fixes found while verifying in a browser: .back-btn-round kept the global button padding (13px 18px) against its fixed 38px box, which squeezed the icon to zero width as soon as it was used on a <button>; and the hero's grain and blur, tuned for a small card, looked dirty at this scale. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
The fade hit rgba(0,0,0,0.55) at 74% of the frame, so the bottom quarter of the photo died into flat black and the transition read as a wall rather than a gradient; the remaining 18px margin then left a band of empty black above the title. Hold the frame clean to ~62% and move the whole falloff to the last fifth, with the closing stops mixed on the page background (--bg) instead of pure black so it doesn't haze over the dark page. Pull the title block up 36px so it sits inside that falloff: the photo now runs into the title instead of ending above it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
A darkening overlay can never fully hide where the photo stops: at the bottom edge the image is cut at full opacity underneath, and on bright parts of the frame that seam stays visible however gentle the gradient is. Fade the photo itself with a mask-image down to fully transparent, so the alpha reaches zero exactly at the edge and there is nothing left to cut. The falloff starts around the middle of the frame and uses closely spaced stops approximating an ease curve, which keeps it from banding. The old overlay stays only as a small top scrim so the floating buttons keep their contrast on bright photos. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
Hold full opacity only through the top ~18% and spread the falloff across the rest, so the photo dissolves into the page over most of its height instead of over the bottom third. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S5vnmcgfqY6rNjwwjiE3fB
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.
nag był obliczany useMemo po 'if (loading) return', więc przy przejściu
loading true->false rosła liczba hooków i React się wywalał. Przeniesiono
useMemo nad wczesne returny, do reszty hooków.
Co-Authored-By: Claude Opus 4.8 noreply@anthropic.com
Claude-Session: https://claude.ai/code/session_01MF3pEjMiVuE3XnzgNeXfR2