GoSki
A ski tracker that names every run, 3D trail maps of 43 resorts, snow forecasts and Real Time lift alerts.
翻譯準備中
尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證Projeto destinado ao meu controle de treinos
翻譯準備中
取自儲存庫 README
正在載入完整 README。
檢視完整 README ↗Aplicativo web para gerenciar treinos de musculação com foco em hipertrofia: rotina semanal editável, execução de treino ao vivo com registro de séries, progressão dupla de carga, estatísticas, histórico, recuperação estimada por grupo muscular e biblioteca de exercícios.
Requisitos: Node.js 18.18+ (recomendado 20 ou 22) e npm.
npm install
npm run dev
Abra http://localhost:3000. Sem configurar nada, o app roda em modo local: os dados ficam no localStorage do navegador e não são sincronizados.
| Script | O que faz |
|---|---|
npm run dev | Servidor de desenvolvimento |
npm run build | Build de produção (inclui checagem de tipos) |
npm run start | Serve o build de produção |
npm run typecheck | tsc --noEmit |
npm run lint | ESLint (regras next/core-web-vitals + next/typescript) |
npm run test | Testes unitários com Vitest (cálculos, persistência, backup) |
npm run check | typecheck + lint + test + build |
npm run vapid | Gera o par de chaves VAPID para as notificações push (uma vez por projeto) |
Copie .env.example para .env.local. Nenhuma variável é obrigatória.
| Variável | Obrigatória | Descrição |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL | não | URL do projeto Supabase. Ativa o modo nuvem quando presente junto da chave. |
NEXT_PUBLIC_SUPABASE_ANON_KEY | não | Chave pública anon. Nunca use a service_role no frontend. |
NEXT_PUBLIC_ALLOW_LOCAL_MODE | não | Com o Supabase configurado a conta é obrigatória. true volta a oferecer "Usar modo local neste dispositivo" na tela de login. |
NEXT_PUBLIC_VAPID_PUBLIC_KEY | não | Chave pública VAPID (Web Push). Ativa a notificação de fim de descanso com a tela bloqueada. Ver seção 11. |
VAPID_PRIVATE_KEY | não | Chave privada VAPID. Só no servidor (sem prefixo NEXT_PUBLIC_): é usada pela rota /api/rest-timer para assinar os pushes. |
VAPID_SUBJECT | não | Contato do responsável exigido pelo protocolo (mailto:... ou https://...). |
Chaves privadas nunca chegam ao navegador: a service_role do Supabase não é usada em lugar nenhum e a VAPID_PRIVATE_KEY só é lida pela rota de API. Toda a segurança de dados no Supabase vem das políticas RLS (ver migrations).
| Modo local | Supabase | |
|---|---|---|
| Quando | Variáveis ausentes (ou NEXT_PUBLIC_ALLOW_LOCAL_MODE=true e o usuário escolheu "Usar modo local") | Variáveis presentes; login obrigatório |
| Onde salva | localStorage com prefixo ironlog:v1: | Tabela public.user_documents (uma linha por chave) |
| Sincroniza entre dispositivos | Não (o app deixa isso explícito na interface) | Sim |
| Backup | Exportar/importar JSON em Perfil | Idem, além da nuvem |
A camada de persistência é isolada (lib/storage/): ambos os modos salvam os mesmos seis documentos JSON (settings, exercises, routine, sessions, activeSession, restLogs). Migrar do local para a nuvem é exportar o backup e importar após entrar na conta.
supabase/migrations/0001_init.sql. Ele cria a tabela user_documents, habilita RLS e cria políticas para que cada usuário leia/escreva somente as próprias linhas. Alternativamente, com a CLI: supabase db push..env.local:
NEXT_PUBLIC_SUPABASE_URL=https://xxxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJ...
npm run dev. A tela inicial passa a pedir login (ou permite continuar em modo local).vercel.json já define npm run build).NEXT_PUBLIC_SUPABASE_URL e NEXT_PUBLIC_SUPABASE_ANON_KEY para os ambientes Production/Preview. Para notificações com a tela bloqueada, adicione também NEXT_PUBLIC_VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY e VAPID_SUBJECT (seção 11).npm i -g vercel
vercel login
vercel # primeiro deploy (preview)
vercel --prod # produção
Para variáveis: vercel env add NEXT_PUBLIC_SUPABASE_URL production (repita para a chave e para preview).
Em Authentication → URL Configuration no Supabase:
https://ironlog.vercel.app). É para onde o link de confirmação de e-mail leva por padrão; se ficar em http://localhost:3000, o usuário cai no localhost após confirmar.http://localhost:3000/** (desenvolvimento) e, se quiser previews, https://*.vercel.app/**.O app envia emailRedirectTo com a origem em que o cadastro foi feito, mas o Supabase só respeita origens presentes em Redirect URLs.
Implementadas em lib/volume.ts, lib/progression.ts, lib/recovery.ts e lib/stats.ts, com testes em tests/.
+incremento (2,5 kg superiores / 5 kg inferiores, configuráveis). Caso contrário sugere manter; se a maioria das séries ficou abaixo do mínimo, sugere reduzir. Sugestões nunca são apresentadas como obrigatórias.carga × (1 + reps/30).YYYY-MM-DD local para registros de descanso.app/ rotas (App Router) — páginas finas que montam componentes
api/rest-timer/ agenda/cancela o push de fim de descanso (responde e segue viva até o envio)
components/
layout/ AppShell (sidebar/bottom nav), AuthScreen, ServiceWorkerRegister
ui/ primitivos: Button, Card, Field, Dialog, Toast, Badge, Stat…
dashboard/ week/ workout/ history/ progression/ recovery/ exercises/ settings/
charts/ Recharts (LoadProgress, WeeklyVolume, Frequency, MuscleVolume)
lib/
store/AppProvider.tsx estado global + ações + salvamento automático por chave
storage/ StorageAdapter, LocalStorageAdapter, SupabaseStorageAdapter, repository (load/validate)
validation/ esquemas Zod (entidades, formulários, backup)
push/ notificações: shared (regras), client (navegador), server (núcleo testável da rota)
hooks/ useWakeLock, useRestPush
supabase/ client (navegador, chave anon) e server (cliente por requisição com o JWT do usuário)
volume.ts progression.ts recovery.ts stats.ts records.ts session.ts csv.ts backup.ts
types/ tipos do domínio
data/ biblioteca de exercícios e rotina inicial
public/sw.js service worker: só notificações (sem cache de páginas)
supabase/migrations/ SQL + políticas RLS (0001 documentos; 0002 cronômetro de descanso)
tests/ Vitest
Persistência: o AppProvider guarda o AppData em memória e, a cada alteração, salva somente as chaves que mudaram (debounce de 400 ms; 120 ms para a sessão ativa). Também salva em pagehide/visibilitychange. Falhas de gravação aparecem em um banner com botão de tentar novamente; dados corrompidos são substituídos pelo padrão e reportados.
npm run typecheck # TypeScript strict, noUncheckedIndexedAccess
npm run lint # ESLint
npm run test # 58 testes: volume, progressão, recordes, estatísticas, recuperação, persistência, backup, CSV, demos, push
npm run build # build de produção
Smoke test de ponta a ponta (opcional): scripts/e2e-smoke.mjs percorre o fluxo real no navegador (iniciar treino, registrar séries, recarregar e conferir persistência, finalizar, histórico, progressão, recuperação, criar exercício, editar semana, trocar tema) e falha se houver erros de console. Requer o Playwright e um Edge/Chrome instalado:
npm run dev # em um terminal
npm i -D playwright # uma vez (sem baixar navegadores)
BASE_URL=http://localhost:3000 node scripts/e2e-smoke.mjs
No Windows (PowerShell): $env:BASE_URL="http://localhost:3000"; node scripts/e2e-smoke.mjs. O script usa o canal msedge; troque por chrome em chromium.launch se preferir. Há também scripts/e2e-nav.mjs (navegação mobile), scripts/e2e-auth.mjs (tela de login obrigatória) e scripts/e2e-push.mjs (service worker, card de notificações, Wake Lock e respostas da rota /api/rest-timer).
Quando o cronômetro zera, o app avisa em três camadas. Cada uma cobre um cenário diferente; nenhuma substitui a outra.
| Camada | Quando funciona | O que precisa |
|---|---|---|
| Som + vibração no cronômetro | App aberto na tela | Nada (vibração não existe no iPhone; o som exige um toque prévio na página) |
| Tela acesa (Wake Lock) | Durante todo o treino ao vivo | Ligado por padrão em Perfil → "Manter a tela acesa". Evita que o aparelho bloqueie e congele timer/som. É a forma mais confiável no iPhone. |
| Notificação local | App em segundo plano (outra aba/app), enquanto o navegador mantiver a página viva | Permissão de notificações (Perfil → Notificações do descanso) |
| Push agendado pelo servidor | Tela bloqueada ou app fechado | Permissão + conta Supabase + chaves VAPID + migration 0002_rest_timers.sql. No iPhone/iPad: app instalado na Tela de Início |
POST /api/rest-timer com a inscrição de push e o horário em que o descanso termina.rest_timers, responde 202 na hora e continua executando com after() do Next até o fim do descanso.DELETE ou novo POST). Ao acordar, a função relê o token: se mudou, não envia nada.public/sw.js) mostra a notificação "Descanso finalizado — bora para a próxima série!". Se o app estiver visível, não mostra (o cronômetro já avisou). Tocar na notificação abre /treino.Por que esse desenho: a Web não tem API de notificação agendada no cliente; setTimeout morre com a aba suspensa; o Push é a única entrega que acorda o aparelho bloqueado. Manter a função viva até o envio dispensa filas externas e cron (que têm granularidade de 1 minuto). O limite é a duração máxima da função: 300 s no plano Hobby da Vercel com Fluid Compute (maxDuration = 300 na rota), então descansos acima de 4min50s não são agendados no servidor (as outras camadas seguem funcionando). Em projetos antigos da Vercel sem Fluid Compute o limite cai para 60 s: ative em Project Settings → Functions.
npm run vapid # imprime publicKey / privateKey
.env.local: NEXT_PUBLIC_VAPID_PUBLIC_KEY, VAPID_PRIVATE_KEY, VAPID_SUBJECT=mailto:seu@email. Depois, redeploy (a chave pública entra no build).supabase/migrations/0002_rest_timers.sql.iPhone/iPad: o Safari só permite notificações para apps instalados. Compartilhar → "Adicionar à Tela de Início", abrir pelo ícone e então ativar em Perfil. O app já envia os metadados necessários (manifest com display: standalone, apple-touch-icon, apple-mobile-web-app-capable). Vibração não é suportada no iOS; o som da notificação segue o sistema.
Android (Chrome/Edge/Samsung): funciona no navegador normal ou instalado. A economia de bateria agressiva de algumas marcas pode atrasar pushes; coloque o navegador como "sem restrição" se os avisos atrasarem.
A preferência é por dispositivo (a permissão também é) e fica no localStorage, não em Settings. O service worker não faz cache de páginas, para nunca servir uma versão antiga do app.
| Sintoma | Causa provável | Solução |
|---|---|---|
| Tela de login aparece mas eu queria usar sem conta | Supabase configurado | Clique em "Usar modo local neste dispositivo" ou remova as variáveis do .env.local |
Invalid login credentials | E-mail/senha incorretos ou e-mail não confirmado | Confirme o e-mail ou desative a confirmação no Supabase durante testes |
Forbidden use of secret API key in browser ou aviso de "chave SECRETA" no topo | A chave service_role/sb_secret_ foi colocada em NEXT_PUBLIC_SUPABASE_ANON_KEY | Em Project Settings → API Keys, use a chave Publishable (sb_publishable_...) ou a anon legada (eyJ... com role anon). Atualize a variável na Vercel e faça redeploy. O app detecta a chave secreta e cai no modo local por segurança |
| Dados não aparecem em outro navegador | Modo local não sincroniza | Exporte o backup JSON e importe no outro dispositivo, ou configure o Supabase |
new row violates row-level security policy | Migration não aplicada ou usuário sem sessão | Execute supabase/migrations/0001_init.sql e faça login novamente |
| Banner "Armazenamento local cheio" | Cota do localStorage excedida | Exporte um backup e exclua sessões antigas |
| Aviso de múltiplos lockfiles no build | Outro package-lock.json em pasta superior | Já tratado com outputFileTracingRoot; pode remover o lockfile estranho |
| Build falha com erro de tipo | TypeScript strict | Rode npm run typecheck e corrija; o build não ignora erros de tipo por decisão de projeto |
| Som do cronômetro não toca no celular | Navegador bloqueia áudio sem interação | Toque em qualquer botão do timer uma vez; ou desative o som em Perfil |
| Card de notificações diz "faltam as chaves VAPID" | NEXT_PUBLIC_VAPID_PUBLIC_KEY ausente no build | Gere com npm run vapid, configure as três variáveis na Vercel e faça redeploy |
| Teste de notificação: "Aplique a migration 0002" | Tabela rest_timers não existe |
Dados exibidos são sempre os registros reais do usuário. Nenhum gráfico usa valores inventados; estados vazios orientam o que fazer primeiro.
public/exercise-media/data/exercises.tsdata/exercise-demos.tsExecute supabase/migrations/0002_rest_timers.sql no SQL Editor |
| Notificação não chega com a tela bloqueada no iPhone | App aberto no Safari, não instalado | Compartilhar → Adicionar à Tela de Início e abrir por lá; ative as notificações de novo |
| Push chega só depois de ~1 min ou não chega | Função encerrada antes do envio (sem Fluid Compute) ou descanso > 290 s | Ative Fluid Compute em Project Settings → Functions; descansos longos usam só som/notificação local |
| Notificação duplicada (local + push) | Aba voltou a ficar visível no mesmo instante em que o push chegou | Comportamento raro e inofensivo; o service worker suprime o push quando o app está visível |
| Porta 3000 ocupada | Outro processo | npm run dev -- -p 3001 |