4 Commits
Author SHA1 Message Date
Rafael MelgaçoandClaude Opus 5 7934e0d40b feat(marca): o logo do banco chega a tela — o campo deixa de ser decorativo
platform_branding.logo_url e MarcaDeSaida.logoUrl existiam e eram CAMPOS MORTOS:
o unico render lia window.__PUBLIC_ENV__.APP_LOGO_URL, que vem do .env. A tela de
marca salvava um valor que nada mostrava. Sem esta onda, o upload de logo (onda
6) produziria um TERCEIRO campo decorativo.

O seam do cliente passa a receber a marca por PROP: PublicEnvScript deixa de ler
o env cru e recebe o que marcaResolvida() ja monta no servidor. E o consumo do
banco alcanca os 4 call sites CLIENT de branding() — os 2 menus, o painel de
codigos de recuperacao e o de Nuvemshop. Os 4 call sites SERVER (login, signup,
casca e welcome do onboarding) seguem no .env, e isso esta escrito.

DUAS DECISOES QUE SE AFASTAM DA LETRA DO PLANO, com a razao:

1. `||`, nao `??`, no logo da Sidebar. O plano pedia `??` mas justificava com
   "string vazia tem de virar fallback" — as duas coisas nao convivem, porque
   `"" ?? x` devolve `""`. Com `??`, um logo VAZIO vindo de cima APAGARIA o do
   revendedor; com `||` desce, que e o que primeiroDefinido e resolveBranding ja
   fazem nas camadas de baixo. A sabotagem S3 reprova exatamente isso.

2. O <img> foi para app/(public)/layout.tsx, nao para o login/page.tsx: uma casca
   cobre as SEIS telas de auth, incluindo a de recuperacao de senha e a de MFA —
   justamente as que ninguem lembraria de atualizar. E o `alt` usa marca.nome (a
   mesma resolucao que produziu o `src`), nao branding().name: legendar a imagem
   do banco com o nome do .env descreveria outra marca.

PROSA QUE VIROU MENTIRA E FOI CORRIGIDA — e ela era VISIVEL AO OPERADOR: o
paragrafo de /admin/marca dizia que o nome "ainda NAO chega aos menus laterais
nem ao arquivo de codigos de recuperacao". A partir desta onda e falso. Tambem
morreu a razao escrita ao lado de `logo_url: gravada.logo_url` ("valor que
nenhuma tela mostraria").

Sabotagem, previsto vs medido: 2/2, 1/1, 1/1, 2/2, 1/1, 1/1 — seis, zero
divergencia.

DIVIDA DECLARADA COM ESSA PALAVRA no codigo: activeOrg.marca.logoUrl e plumbing
SEM PRODUTOR. camadaDaOrganizacao nao declara logoUrl e o schema da marca da org
nao tem logo_url, entao o ramo origens.logoUrl === "organizacao" e hoje
INALCANCAVEL em producao. O consumidor tem teste de comportamento; o produtor e a
onda 6. Escrito em lib/auth/types.ts e em app/app/layout.tsx para ninguem achar
que ja existe.

typecheck 0 · lint 0 · lint:channels 0 · test:unit 392 files / 4453 passed ·
build 0.

PROVA EM TELA PENDENTE, e nao por falta de trabalho: o disco encheu no meio da
execucao e o daemon do Docker travou como sequela, derrubando o Supabase local.
Prova pendente por infra NAO e prova feita — a guarda de vacuidade ja esta
preparada (o .env NAO tem APP_LOGO_URL; com ele preenchido, ver o logo nao
provaria nada, seria o comportamento antigo).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TCiffR3ugjQbxsfceQE2GB
2026-08-14 12:03:54 -03:00
Rafael MelgaçoandClaude Opus 5 94a5202cc6 feat(branding): marca da instalação configurável por .env — white-label sem editar código
O nome "DeskcommCRM" estava fixo em 11 lugares da interface (layout, entrar,
cadastro, recuperação, MFA, conta suspensa, onboarding, sidebar, sidebar admin).
Quem instala o CRM para clientes — agência, revendedor — só trocava a marca
editando o código-fonte. E patch em código se perde no próximo `update.sh`:
a imagem nova sobrescreve, normalmente sem ninguém perceber até o cliente ver.

Agora são duas variáveis de ambiente, e a marca sobrevive a toda atualização:

    APP_NAME=Vendas Turbo CRM
    APP_LOGO_URL=https://cdn.exemplo.com/logo.svg

SEM prefixo NEXT_PUBLIC_, de propósito — e este é o ponto que faz a feature
existir ou não. NEXT_PUBLIC_* é queimada no bundle durante o `next build`, e o
self-hoster roda uma imagem PRÉ-BUILDADA: a marca dele nunca apareceria. O
mesmo motivo pelo qual a URL do Supabase já é injetada em runtime. `lib/branding.ts`
lê `process.env` no servidor e `window.__PUBLIC_ENV__` no navegador, no mesmo
modelo de `lib/sentry/dsn.ts`; o `<PublicEnvScript/>` passou a injetar as chaves.

Simplificação que veio junto: o root layout já definia `template: "%s · Deskcomm"`
e as páginas filhas escreviam o título completo ("Entrar — DeskcommCRM"), então a
aba renderizava a marca duas vezes. As filhas passam a declarar só o próprio nome
e herdam o sufixo — a marca existe em UM lugar, e trocar uma variável troca todos
os títulos.

Guard contra o defeito silencioso (tests/unit/branding.test.ts): a convenção do
Next empurra qualquer valor lido no browser para NEXT_PUBLIC_*, e alguém vai
"corrigir" isso um dia. O teste falha se o prefixo aparecer, se o PublicEnvScript
parar de injetar as chaves, ou se a marca voltar a ser fixada em .tsx. Sem ele o
defeito passaria em typecheck, lint e na suíte inteira, funcionaria em dev e na
Vercel, e falharia SÓ na VPS de quem a feature existe para servir. As três
sabotagens foram executadas e cada uma vermelheceu.

`install.sh` pergunta o APP_NAME (Enter aceita o padrão) e grava as duas no .env,
que o compose já entrega ao contêiner via `env_file`. Guia para revendedores em
docs/white-label.md, linkado nos 3 READMEs — e honesto sobre o que ainda NÃO é
configurável (cores/fontes exigem patch; a marca é por instalação, não por
organização).

Living System Checklist:
1. Quem me alimenta — o `.env` da instalação (install.sh coleta, operador edita).
2. Quem eu alimento — metadata do root layout, 6 telas públicas, 3 de onboarding,
   Sidebar e AdminSidebar. Onze arestas de saída.
3. Atividade/log — nenhuma, e é deliberado: é configuração de deploy, não mutação
   de dado tenant-aware. Não há ator nem alvo para auditar; a mudança acontece no
   .env do servidor, cuja rastreabilidade é do operador.
4. Onde apareço na tela — a feature É tela: título da aba e marca em toda a UI.
5. Anti-morte — não é demanda aberta, mas a feature tem o seu: o guard de teste
   impede que a marca volte a ser hardcoded e a configuração morra em silêncio.
6. Continuidade IA↔humano — N/A, não toca handoff.
7. Mapa vivo — não altera docs/architecture/: é módulo de apresentação lido do
   ambiente, não peça nova do sistema com arestas de dado.

Verificado: typecheck 0, lint 0 erros, test:unit 1043/1043 (eram 1035; +8 novos).
Prova pela tela com APP_NAME="Vendas Turbo CRM" no dev server: título da aba
"Entrar · Vendas Turbo CRM", marca visível no corpo, ZERO ocorrências de
"DeskcommCRM" na página, e __PUBLIC_ENV__ carregando as duas chaves.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KMEbgy5YWXZimXQAN7oRMm
2026-07-27 13:50:58 -03:00
Rafael MelgaçoandClaude Opus 4.8 07eb106303 chore(oss): Sentry opt-in configurável + licença MIT + política de suporte/LGPD
Sentry (modelo comunidade com guardrails):
- DSN deixa de ser hardcoded; resolveSentryDsn() lê SENTRY_DSN em runtime
  (server/edge via process.env; browser via window.__PUBLIC_ENV__)
- default = Sentry da comunidade; SENTRY_DSN=off desliga; <dsn> aponta pro próprio
- opt-out vale no browser sem rebuild (via PublicEnvScript)
- beforeSend sanitizado (CPF/telefone/e-mail) mantido

Licença: adiciona LICENSE MIT (Copyright 2026 Rafael Melgaço).

README: política de suporte 'as-is', responsabilidades self-host, disclaimer
LGPD (quem hospeda é o controlador) e como ligar/desligar a telemetria.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UHfaYtmvsCjiKfdWDLsMdK
2026-07-06 21:10:55 -03:00
Rafael MelgaçoandClaude Opus 4.8 7edd153f39 feat(hostgator): imagem genérica + config pública em runtime (Opção B)
Elimina o rebuild-por-usuário: uma única imagem serve qualquer projeto Supabase.
O build do Next estoura 2GB e leva ~6min — inviável no VPS baratinho (2GB) que
mais converte. Agora o leigo PUXA a imagem (sobe em ~2min, roda em 2GB).

As NEXT_PUBLIC_* deixam de ser baked:
- app/public-env-script.tsx: server component (await headers() → runtime) injeta
  window.__PUBLIC_ENV__ no <head>
- lib/supabase/browser.ts: lê de window.__PUBLIC_ENV__ (fallback process.env p/ Vercel/dev)
- convites (team/invite, onboarding): env.NEXT_PUBLIC_APP_URL (runtime) em vez de baked
- Dockerfile: NEXT_PUBLIC_* viram placeholders (imagem genérica, sem dado de usuário)
- compose: puxa ghcr.io/...:latest por padrão; build vira override opcional
- .github/workflows/publish-image.yml: CI builda no GitHub → GHCR (build nunca no VPS)

Provado end-to-end: imagem buildada com placeholder.supabase.co, rodada com URL
de runtime distinta, serve window.__PUBLIC_ENV__ com a URL de runtime e zero
placeholder no HTML. tsc limpo = 0 erros; build turbopack ok; standalone gerado.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 12:52:39 -03:00