Este documento é o mapa vivo de cada feature implementada no PetCircle. Para cada domínio funcional você encontra: rotas, Server Actions, componentes principais, tabelas Supabase, triggers de notificação e badge, e edge cases já tratados.
Fonte de verdade do código, não de planejamento. Para roadmap veja
README.md; para spec detalhada de cada feature, vejaspecs/features/.
- Autenticação
- Onboarding
- Feed social
- Perfil do pet
- Co-tutoria e convites
- Saúde e vacinas
- Lembretes e food tracking
- Mensagens diretas
- Explore e busca
- Persona switcher
- Notificações
- Badges e conquistas
- Listas sociais (fãs, petmigos, favoritos)
- Settings e conta
- i18n
- PWA
Login com Google OAuth e email/senha, callback SSR que direciona para onboarding quando o usuário ainda não tem pet.
- Rotas:
src/app/(auth)/login/page.tsx— tela de loginsrc/app/auth/callback/route.ts— OAuth callbacksrc/app/page.tsx— root (redirect para/feedou/login)
- Componentes:
src/app/(auth)/login/LoginButtons.tsx— botão Googlesrc/app/(auth)/login/EmailAuthForm.tsx— form email/senha com signup / forgot password
- Server Actions: nenhuma dedicada; usa
supabase.auth.signInWithOAuth()esignInWithPassword()direto no client. - Tabelas:
auth.users(gerida pelo Supabase) epublic.users(criada via triggerhandle_new_userna migration20260410000001_user_profile_trigger.sql). - Edge cases tratados:
- Conta existente com mesmo email vinda de provider diferente → unificada no
auth.usersdo Supabase - Callback recebe
callbackErrorna query string se falha → redireciona de volta para/logincom mensagem - Se usuário confirmou email mas ainda não tem pet →
/auth/callbackredireciona para/onboarding
- Conta existente com mesmo email vinda de provider diferente → unificada no
Flow de 3 telas para criar o primeiro pet após signup, com animações spring. Também cria o User profile se ainda não existir.
- Rotas:
src/app/(main)/onboarding/page.tsx - Componentes:
src/components/onboarding/OnboardingFlow.tsx— 3 steps (nome+foto → espécie+raça → aniversário+cidade)
- Server Actions (
src/lib/actions/onboarding.ts):createPet(formData)— cria pet + populapet_tutors(o próprio criador como tutor owner)skipPetCreation()— marcaonboarding_skipped_atno user; libera acesso ao feed sem pet
- Tabelas:
users,pets,pet_tutors - Triggers de badge: nenhum (primeira criação não dispara badge; badge
first_postsó depois do primeiro post) - Edge cases:
- Se usuário pular onboarding, pode criar pet depois em
/profileviaAddPetSheet - Slug é gerado automaticamente a partir do nome do pet (
slugify()emsrc/lib/utils/slugify.ts), com retry numérico se colidir
- Se usuário pular onboarding, pode criar pet depois em
Scroll infinito com posts de pets que o usuário segue + seus próprios pets. Likes persona-aware (como tutor ou como pet), comentários com threading 1 nível, mood, double-tap e compartilhamento.
- Rotas:
src/app/(main)/feed/page.tsx— feed principalsrc/app/(main)/post/[id]/page.tsx— landing page dedicada para sharing (comgenerateMetadata)
- Componentes principais:
src/components/feed/FeedList.tsx— scroll infinito, paginaçãosrc/components/feed/PostCard.tsx— card individual com like, comment, sharesrc/components/feed/CreatePostSheet.tsx— bottom sheet de criação com compressão de imagemsrc/components/feed/EditPostSheet.tsx— edição de caption e moodsrc/components/feed/CommentSheet.tsx— thread de comentários + repliessrc/components/feed/LikeButton.tsx— com animação spring e patinha 🐾src/components/feed/SharePostButton.tsx— Web Share API + copy linksrc/components/feed/MoodSelector.tsx— 7 moods (feliz, triste, animado, etc. — definidos emsrc/lib/constants/moods.ts)
- Server Actions:
src/lib/actions/feed.ts:fetchFeedPage(page),fetchFavoriteFeed(page)src/lib/actions/post.ts:createPost(),editPost(),deletePost(),toggleLike()src/lib/actions/comment.ts:createComment(),deleteComment(),getComments(),getReplies()
- Tabelas:
posts(foto + caption + mood),likes(comactor_pet_idnullable para persona),comments(comparent_comment_idpara threading) - Storage: bucket
pet-images(público) - Triggers de notificação:
- Like em post → notif tipo
"like"para tutor(es) do pet dono do post - Comment em post → notif tipo
"comment"para tutor(es) - Reply em comment → notif tipo
"reply"para autor do comment parent
- Like em post → notif tipo
- Triggers de badge:
createPost→ checafirst_posttoggleLike(quando recebe) → checaloved_10createComment→ checasocial_butterfly
- Edge cases:
- Reply de reply é achatado para irmão do parent (threading apenas 1 nível)
- Upload de imagem passa por
compress-image.ts(browser-image-compression, max 1920px, qualidade 0.8) antes de subir pro Storage - Self-like não notifica tutor; mas pet A comentando em pet B do mesmo tutor notifica (regra em
src/lib/notifications/create.ts)
Perfil público do pet com galeria, badges, co-tutores, botões de follow/favorite. SEO via generateMetadata com Open Graph. URLs canonicalizadas por slug.
- Rotas:
src/app/(main)/pet/[id]/page.tsx— aceita UUID ou slug; se UUID e pet tem slug, redireciona para canonical URL - Componentes principais:
src/components/pet/pet-profile-header.tsx— avatar, nome, contadoressrc/components/pet/pet-info-section.tsx— bio, personalidade, idade, cidadesrc/components/pet/PetProfileStats.tsx— posts / fãs / petmigossrc/components/pet/pet-gallery.tsx— grid de postssrc/components/pet/PetEditDrawer.tsx— edição (nome, raça, bio, avatar, data, cidade)src/components/pet/PetDeleteSection.tsx— soft/restore/hard deletesrc/components/pet/FollowButton.tsx— follow/unfollowsrc/components/pet/FavoriteButton.tsx— marcar pet como petmigosrc/components/pet/TutorSection.tsx— lista de co-tutores + invitessrc/components/badges/BadgeSection.tsx— carrossel de badges earned
- Server Actions (
src/lib/actions/pet.ts):addPet(formData)— criar pet novoupdatePet(petId, formData)— editarcheckSlugAvailability(slug)— validação durante ediçãodeletePet(petId)— soft-delete (30 dias de grace)restorePet(petId)— desfaz soft-deletehardDeletePet(petId)— deleta permanentemente (admin ou após 30d)
- Follow:
src/lib/actions/follow.ts→toggleFollow(petId) - Tabelas:
pets,pet_tutors,follows,pet_badges - Storage: bucket
pet-imagespara avatars - Triggers de notificação: follow recebido → notif tipo
"follow"para tutor(es) do pet - Triggers de badge: follow recebido → checa
popular_50,influencer_100 - Edge cases:
generateMetadatabusca pet por ID ou slug; retorna OpenGraph com avatar como imagem- Soft-delete marca
deleted_at;purge_cron(migration20260415000007) remove após 30 dias - Se pet já tem slug e acessado por UUID → redirect para URL canônica
- Count cacheado em colunas (
followers_count,posts_count) via triggers Postgres, atualizado automaticamente
Múltiplos tutores podem compartilhar um mesmo pet. Invites são links com código que expiram em 30 dias.
- Rotas:
src/app/invite/[code]/page.tsx— landing de aceitar convite
- Componentes:
src/components/pet/TutorSection.tsx— UI principal dentro do pet profilesrc/components/invite/AcceptInviteCard.tsx— card de aceitar convite
- Server Actions (
src/lib/actions/tutor.ts):createInvite(petId)— gera código, salva empet_invitescomexpires_at = now() + 30dgetInviteByCode(code)— busca invite válidoacceptInvite(code)— cria linha empet_tutors, marca invite como usadoremoveTutor(petId, userId)— remove relação (se owner ou self)getTutors(petId)— lista tutores do petupdateShowOnProfile(petId, show)— toggle de exibição na lista públicagetVisibleTutors(petId)— lista filtrada para perfil público
- Tabelas:
pet_tutors,pet_invites - Edge cases:
- Invite expirado → rota
/invite/[code]mostra mensagem "convite expirado" - Invite já usado → mensagem similar
- Owner não pode ser removido (precisa transferir ownership primeiro — não implementado; limitação conhecida)
- Invite expirado → rota
Carteirinha de vacinação com data de aplicação, próxima dose, veterinário, lote e receita em PDF/imagem.
- Rotas: integrado ao pet profile (sem rota dedicada)
- Componentes:
src/components/health/HealthTimeline.tsx— timeline de eventossrc/components/health/TimelineEventItem.tsx— card de eventosrc/components/health/VaccineList.tsx— lista de vacinassrc/components/health/VaccineCard.tsx— card de vacinasrc/components/health/CreateVaccineSheet.tsx— form de criaçãosrc/components/health/EditVaccineSheet.tsx— ediçãosrc/components/health/VaccineReceiptUpload.tsx— upload de receita
- Server Actions:
src/lib/actions/vaccination.ts:getVaccinations(petId),createVaccination(),updateVaccination(),deleteVaccination(),uploadVaccineReceipt(),deleteVaccineReceipt()src/lib/actions/health-timeline.ts:getHealthTimeline(petId)— combina vacinas + lembretes em ordem cronológica
- Tabelas:
vaccinations - Storage: bucket
vaccine-proofs(privado — policies RLS) - Triggers de badge:
vaccine_updated→ checavaccinated(todas vacinas em dia) - Edge cases:
- Upload de receita sobrescreve anterior (deleta storage path antigo antes)
- Validação Zod: data aplicada não pode ser futura; próxima dose tem que ser após data aplicada
Lembretes per-pet, all-pets ou personal, com frequências variadas. Food tracking especializado com sugestão baseada em histórico de compras.
- Rotas:
src/app/(main)/reminders/page.tsx - Componentes:
src/components/reminders/RemindersPageClient.tsx— shell com tabssrc/components/reminders/ReminderList.tsx— lista agrupada por tiposrc/components/reminders/CreateReminderSheet.tsx— form de criação (tipo, escopo, frequência, horários)src/components/reminders/FoodRemindersSection.tsx— seção especializadasrc/components/reminders/FoodPurchaseSheet.tsx— registrar compra de ração
- Server Actions:
src/lib/actions/reminder.ts:getReminders(),createReminder(),completeReminder(),toggleReminderActive()src/lib/actions/food.ts:createFoodPurchase(),deleteFoodPurchase(),getFoodHistory(petId),getSmartSuggestion(petId),getActiveFoodReminders()
- Tabelas:
reminders,reminder_completions,food_purchases - Triggers de badge:
reminder_completed→ checaconsistent_7(7+ lembretes completados) - Edge cases:
- Escopo
all-petscria 1 lembrete compet_id = nullmasis_all_pets = true; na listagem, aparece em cada pet getSmartSuggestionestima próxima compra com base em intervalo médio de compras anteriores- Completion é tracked em
reminder_completionscom(reminder_id, date)único — 1 completion por dia
- Escopo
Chat 1:1 entre tutores (não entre pets). Realtime via Supabase, presença, envio como pet ou como tutor.
- Rotas:
src/app/(main)/messages/page.tsx— lista de conversassrc/app/(main)/messages/[id]/page.tsx— chat individualsrc/app/api/messages/[conversationId]/route.ts— API para paginação de mensagens antigas (scroll up)
- Componentes:
src/components/messages/ChatView.tsx— view principal com scroll, realtime, presencesrc/components/messages/ChatInput.tsx— textarea com auto-grow + persona selectorsrc/components/messages/MessageBubble.tsx— bubble com avatarsrc/components/messages/QuickEmojiBar.tsx— emojis rápidossrc/components/messages/ConversationList.tsx— lista com unread badge
- Hooks:
src/lib/hooks/useRealtimeMessages.ts— subscribe apostgres_changesemmessagessrc/lib/hooks/usePresence.ts— marca usuário como online na conversasrc/lib/hooks/useUnreadMessagesCount.ts— subscription para badge da BottomNav
- Server Actions (
src/lib/actions/message.ts):getOrCreateConversation(otherUserId)— normalizauser_a < user_b, cria se não existegetConversations()— lista com preview da última mensagemsendMessage(conversationId, body, asPetId?)— envia + dispara pushmarkAsRead(conversationId)— zera unreadgetUnreadCount()— total para badge
- Presence (
src/lib/actions/presence.ts):setPresence(conversationId, online)— atualizauser_presencetoggleMessagePush(enabled)/getMessagePushEnabled()— preferência de push para DMs
- Tabelas:
conversations,messages,user_presence - Realtime:
postgres_changesemmessagesenotifications - Triggers de notificação: nova mensagem → push notification direto (não cria notif DB) se destinatário não está ativo na conversa + tem
message_push_enabled - Edge cases:
- Conversation ID é determinístico via ordenação de UUIDs (previne duplicata)
- Self-message é bloqueado no action
- Se usuário está em a conversa (presence online) → não envia push (evita notificação enquanto já vê a mensagem)
Grid de pets populares + busca por nome/raça com debounce. Filtros por espécie. Seção "Perto de você" baseada na cidade.
- Rotas:
src/app/(main)/explore/page.tsx - Componentes:
src/components/explore/ExploreSearch.tsx— search input + filtros + resultadossrc/components/explore/ExplorePetCard.tsx— card de petsrc/components/explore/ExplorePetGrid.tsx— grid paginado
- Server Actions:
src/lib/actions/explore.ts:searchPets(query, species?, page, cityId?)— usa índices pg_trgm para busca fuzzysrc/lib/actions/city.ts:searchCities(query)— autocomplete
- Tabelas:
pets(com índices pg_trgm emname,breed),cities - Edge cases:
- Query vazia → lista por
followers_count DESC - Query > 2 chars → usa
ilike+ índice trigram - Debounce 300ms no client
- Exclui pets do próprio usuário e pets com
deleted_at NOT NULL
- Query vazia → lista por
Sistema que permite ao usuário interagir como tutor ou como um de seus pets. Afeta likes, comentários e mensagens.
- Arquivos:
src/lib/persona/PersonaContext.tsx— React contextsrc/lib/persona/persona-cookie.ts— leitura/escrita do cookieactive-personasrc/lib/persona/persona-server.ts— leitura server-sidesrc/components/layout/PersonaSwitcherSheet.tsx— UI de troca
- Cookie:
active-persona="tutor"ou"pet:{petId}" - Uso em actions:
toggleLike(postId)→ lê persona, se é pet salvaactor_pet_idcreateComment(...)→ similarsendMessage(...)→ salvasender_pet_idse enviando como pet
- Edge cases:
- Persona persiste entre sessões (cookie httpOnly com 1 ano)
- Ao deletar um pet, se o pet ativo for o deletado, persona volta para
"tutor" - SSR lê cookie em Server Components para renderizar UI correta antes do hydration
Centro de notificações in-app + Web Push para mensagens. Regras de self-action para não spammar o próprio usuário.
- Rotas:
src/app/(main)/notifications/page.tsx - Componentes:
src/components/notifications/NotificationList.tsx— lista paginada com infinite scrollsrc/components/notifications/NotificationItem.tsx— item individual com deep link
- Server Actions (
src/lib/actions/notification.ts):getNotifications(page)— com join de users/pets relacionadosgetUnreadNotificationCount()— badgemarkNotificationAsRead(id)— individualmarkAllNotificationsAsRead()— bulkdeleteNotification(id)— remove
- Push subscription (
src/lib/actions/push.ts):savePushSubscription(subscription)— salva empush_subscriptionsremovePushSubscription(endpoint)— unsubscribegetPushStatus()— verifica se user tem subscription ativa
- API Routes:
src/app/api/push/subscribe/route.ts— POST recebe subscriptionsrc/app/api/push/unsubscribe/route.ts— POST remove
- Lógica centralizada:
src/lib/notifications/create.ts→shouldCreateNotification()aplica regras de self-actionsrc/lib/notifications/recipients.ts→getNotificationRecipients(petId, actorUserId)lista tutores a notificarsrc/lib/push/send.ts→sendPushNotification(userId, payload)envia via web-push + VAPID
- Tabelas:
notifications,push_subscriptions,user_presence(para skip push se online na conversa) - Realtime: subscription em
notificationspara atualizar badge sem refresh - Para referência completa: veja
docs/NOTIFICATIONS.md
8 badges automáticos, desbloqueados em triggers específicos. Notificação "badge" quando earned.
- Badges definidos:
first_post,loved_10,popular_50,influencer_100,vaccinated,consistent_7,birthday,social_butterfly - Componentes:
src/components/badges/BadgeSection.tsx— carrossel no pet profilesrc/components/badges/BadgeModal.tsx— modal com todas (earned + locked)src/components/badges/BadgeItem.tsx— item individual
- Server Actions (
src/lib/actions/badge.ts):getPetBadges(petId)— earnedgetAllBadges()— todas as definiçõescheckAndGrantBadges(petId, trigger)— checa condições e concede; chamada após cada evento relevante
- Triggers:
post_created,like_received,follow_received,vaccine_updated,reminder_completed - Tabelas:
badges(seed),pet_badges(earned) - Para referência completa: veja
docs/BADGES.md
Sheets que listam quem curtiu um post, quem são os fãs de um pet, ou quem favoritou ele.
- Componentes:
src/components/lists/LikesSheet.tsx— usuários que curtiram um postsrc/components/lists/FansSheet.tsx— fãs do pet (quem segue)src/components/lists/PetmigosSheet.tsx— petmigos (quem o pet segue)src/components/lists/FavoritesSheet.tsx— posts favoritados
- Server Actions (
src/lib/actions/user-lists.ts):getPetFans(petId, page)— seguidores do petgetPetFavorites(petId, page)— favoritos do petgetPostLikes(postId, page)— likes em um post
- Tabelas:
follows,likes
Edição de perfil do tutor, preferências (idioma, push), gestão de conta (delete account com grace period).
- Rotas:
src/app/(main)/settings/page.tsx— settings principalsrc/app/(main)/settings/about/page.tsx— sobre (versão, links)src/app/(main)/profile/page.tsx— perfil do tutor
- Componentes:
src/components/settings/SettingsClient.tsx— shellsrc/components/settings/LanguageSheet.tsx— switch pt-BR ↔ ensrc/components/settings/PushNotificationToggle.tsx— toggle global de pushsrc/components/settings/MessagePushToggle.tsx— toggle específico de DMssrc/components/profile/AddPetSheet.tsx— criar pet (fora de onboarding)src/components/profile/ProfilePetCard.tsx— card de pet na lista do tutor
- Server Actions:
src/lib/actions/user.ts:updateUserProfile(formData)— nome, avatar do tutorsrc/lib/actions/locale.ts:setLocaleCookie(locale)src/lib/actions/account.ts:requestAccountDeletion(),cancelAccountDeletion(),hardDeleteAccount()
- Tabelas:
users(comdeletion_requested_atpara grace period) - Edge cases:
- Request delete marca
deletion_requested_at = now();purge_cronroda após 30 dias - Cancel delete zera
deletion_requested_at - Hard delete imediato disponível (requer confirmação)
- Request delete marca
next-intl com pt-BR (default) + en. Cookie-based, sem URL prefix. Zero hardcoded strings (regra obrigatória).
- Setup:
src/i18n/request.ts— config do next-intlsrc/messages/pt-BR.json+src/messages/en.json— ~500 keys organizadas por namespace- Cookie
NEXT_LOCALEcontrola idioma
- Script de validação:
scripts/check-i18n.tsrodado viapnpm i18n:check- Garante keys idênticas em ambos locales
- Valida variáveis de interpolação (
{count},{petName}) coincidem
- Componentes:
- Client:
useTranslations("namespace")denext-intl - Server:
getTranslations("namespace")denext-intl/server
- Client:
- Regras completas:
.claude/rules/i18n.md
Manifest + service worker custom (sem next-pwa/serwist). Ícones maskable, push support, offline cache dos shell assets.
- Arquivos:
public/manifest.json— manifest comdisplay: standalonepublic/sw.js— service worker (install/activate/fetch/push/notificationclick)public/icons/— 192/512 em PNG + SVG, maskablesrc/components/ui/ServiceWorkerRegistration.tsx— registra o SW no mountsrc/app/layout.tsx— metadata PWA + apple web app
- Cache strategy: cache-first para STATIC_ASSETS (shell), network-first para tudo mais
- Push handler: ouve evento
push, exibe notificação com title/body/icon/badge/url - Para referência completa: veja
docs/PWA.md
| Feature | Tabelas principais |
|---|---|
| Auth | auth.users, users |
| Onboarding | users, pets, pet_tutors |
| Feed | posts, likes, comments |
| Pet profile | pets, pet_tutors, follows, pet_badges |
| Co-tutoria | pet_tutors, pet_invites |
| Saúde | vaccinations |
| Lembretes | reminders, reminder_completions, food_purchases |
| Mensagens | conversations, messages, user_presence |
| Explore | pets, cities |
| Notificações | notifications, push_subscriptions |
| Badges | badges, pet_badges |
| Feature | Actions principais | Arquivo |
|---|---|---|
| Feed | fetchFeedPage, fetchFavoriteFeed |
feed.ts |
| Posts | createPost, editPost, deletePost, toggleLike |
post.ts |
| Comentários | createComment, deleteComment, getComments, getReplies |
comment.ts |
| Pets | addPet, updatePet, deletePet, restorePet, hardDeletePet, checkSlugAvailability |
pet.ts |
| Onboarding | createPet, skipPetCreation |
onboarding.ts |
| Follow | toggleFollow |
follow.ts |
| Co-tutoria | createInvite, acceptInvite, removeTutor, getTutors, getInviteByCode, updateShowOnProfile, getVisibleTutors |
tutor.ts |
| Saúde | getVaccinations, createVaccination, updateVaccination, deleteVaccination, uploadVaccineReceipt, deleteVaccineReceipt |
vaccination.ts |
| Health Timeline | getHealthTimeline |
health-timeline.ts |
| Lembretes | getReminders, createReminder, completeReminder, toggleReminderActive |
reminder.ts |
| Food | createFoodPurchase, deleteFoodPurchase, getFoodHistory, getSmartSuggestion, getActiveFoodReminders |
food.ts |
| Mensagens | getOrCreateConversation, getConversations, sendMessage, markAsRead, getUnreadCount |
message.ts |
| Presence | setPresence, toggleMessagePush, getMessagePushEnabled |
presence.ts |
| Explore | searchPets |
explore.ts |
| Cities | searchCities |
city.ts |
| Notificações | getNotifications, getUnreadNotificationCount, markNotificationAsRead, markAllNotificationsAsRead, deleteNotification |
notification.ts |
| Push | savePushSubscription, removePushSubscription, getPushStatus |
push.ts |
| Badges | getPetBadges, getAllBadges, checkAndGrantBadges |
badge.ts |
| Listas | getPetFans, getPetFavorites, getPostLikes |
user-lists.ts |
| User | updateUserProfile |
user.ts |
| Conta | requestAccountDeletion, cancelAccountDeletion, hardDeleteAccount |
account.ts |
| Locale | setLocaleCookie |
locale.ts |
Para assinaturas detalhadas de cada action (parâmetros, retorno, efeitos colaterais), veja docs/API-REFERENCE.md.