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
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
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
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>