feat(results): botão "Compartilhar" na tela de Resultado — fecha o loop viral (#22) - #57
Conversation
A tela de Resultado tinha só "Jogar de novo" e o link do ranking — cada corrida terminada era um convite desperdiçado, apesar de a home inteira vender "manda o link pros amigos" e do lobby já ter copiar-link. - `src/lib/share.ts`: helper puro `buildShareText(me, url)` (sem `window`), com `SITE.url` só como default de contexto sem browser. - `src/lib/share.test.ts`: 9 casos (com/sem colocação, arredondamento, URL custom, nada privado no texto). - `Results.tsx`: `ShareButton` secundário ao lado do CTA primário — Web Share API quando existe, clipboard + toast como fallback. Refs #22
|
@caioross is attempting to deploy a commit to the caioross' projects team on Vercel, but is not a member of this team. To resolve this issue, you can:
To read more about collaboration on Vercel, click here. |
|
Caution The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased. |
🩺 PR Doctor — revisada e APROVADA · na fila de merge da próxima rodadaDiff lido inteiro (3 arquivos). Classificação §7.3 Normal: tela pós-corrida, helper puro e O que confirmei no código (não só no corpo da PR):
Nota da PR que já pode ser riscada: o alerta sobre Por que não mergeei nesta rodada: teto de 2 merges/rodada (HANDBOOK §8) já usado com #52 e |
✅ Parecer PR Doctor — APROVADA (Normal §7.3)Diff lido inteiro. Classificação Normal: tela pós-corrida, zero contato com a área sagrada (input/textarea da corrida), sem tocar
|
Contexto
A persona O Iniciante (D#14) nomeou como encantamento nº1 ver o próprio nome no ranking logo após terminar. A home vende "manda o link pros amigos" e já existe copiar-link no lobby e no
RoomCodePill— faltava o elo mais óbvio: compartilhar o RESULTADO. Cada corrida terminada era um convite desperdiçado.Segui o Parecer do Conselho (2026-07-22) quase à risca, incluindo os 3 ajustes ao AC.
O que mudou
src/lib/share.ts(novo) —buildShareText(me: Player, url = SITE.url): string, puro e semwindow. Complace:Fiz 85 WPM (98%) e terminei em 1º no CodeRacer 🏁 me alcança: <url>; semplace: mesma frase sem o trecho de colocação.src/lib/share.test.ts(novo) — 9 casos no padrão deroom.test.ts: com/sem colocação,place: 0tratado como "não terminou", arredondamento de fracionários, URL custom vs. default, e a garantia de que nem o nick nem o código da sala entram no texto.Results.tsx—ShareButtonrenderizado dentro deme && (...), ao lado do CTA. Usanavigator.share({text, url})quando existe; senão,navigator.clipboard.writeText+ toast, copiando o par.then/.catchdeRoomView.tsxem vez de reinventar. A área de ações virou umflex-wrappara acomodar os dois botões.Decisões (as 3 do parecer, mais uma)
navigator.sharerejeita comAbortErrorquando o jogador fecha o sheet nativo; copiar mesmo assim seria hostil. O fallback para clipboard só roda quandoerr.name !== "AbortError". Isso diverge do AC ("fallback quando cancelado") — o parecer é a justificativa.btn-secondary, nãobtn-primary— "Jogar de novo" continua sendo a ação primária; o share é o convite.aria-label="Compartilhar meu resultado", focável por teclado,whileHover/whileTapidênticos ao botão vizinho (nenhuma animação nova).window.location.originno handler, nãoSITE.url— este último cai emhttps://coderacer.app(que não é o domínio de produção) quandoNEXT_PUBLIC_SITE_URLnão está no build.SITE.urlfica só como default do helper puro.Segurança: o texto carrega apenas nick-nenhum/WPM/precisão/colocação — dados já públicos no
/leaderboard. O código da sala não entra (sala é efêmera; um/room/<code>no post daria entrada a qualquer um) e há teste travando isso. Zero dependência nova.Validação (resultado real)
pnpm lint= N/A (não há config ESLint no repo; a CI também não roda lint).Verificação em browser: não foi feita — e isso é honesto, não omissão.
Results.tsxsó é alcançável depois de uma corrida multiplayer completa (lobby → countdown → racing → finished) contra um Supabase real; é exatamente o ponto cego que a #37 existe para resolver, e o Retro W28 já registrou 3 PRs de UI nessa mesma condição. Mitigação: toda a lógica não-visual está em helper puro coberto por Vitest, e o botão reusa classes (btn-secondary) e padrão de motion já em produção.Riscos
textarea).useToastcai num fallback silencioso fora de<ToastProvider>(ui/Toast.tsx:48-52). Sob/room/[id]o provider existe (page.tsx:19), então o toast funciona. SeResultsfor reusada em outra rota — ex.:/practiceda PR feat(practice): rota /practice com Treino Livre solo, sem sala (#25) #40 — o provider precisa acompanhar, senão o toast some sem erro. Vale como nota para quem mergear a feat(practice): rota /practice com Treino Livre solo, sem sala (#25) #40.flex-wrap; em telas muito estreitas os dois botões empilham (comportamento desejado).Closes #22