Pular para o conteúdo

Configurações do kit em /admin

O que a instalação perguntou — e mais um punhado de coisas que antes só se mudava editando arquivo — vive em /admin/configuracoes-da-aplicacao — no menu do painel a tela se chama Configurações da aplicação, porque depois de instalado o kit é a procedência, não o produto —, em seis abas. Nada de .env, nada de deploy.

Todo campo fica numa seção com ícone, colapsável pelo cabeçalho; o navegador lembra o estado de cada seção (fechada ou aberta, inclusive as que nascem fechadas) em localStorage, por navegador. Cada aba carrega seu conteúdo só quando é aberta, e uma aba com erro de validação abre sozinha.

Cada aba tem chave fixa na URL — ?tab=identidade, ?tab=email, ?tab=tabelas, ?tab=registro, ?tab=login, ?tab=aplicacao —, estável para link e favorito. Até a v0.49 a chave era gerada pelo Filament (?tab=…::data::tab); um link antigo nesse formato não quebra, só abre na primeira aba.

Aba O que você troca Seções
Identidade nome da aplicação, versão do sistema, cor primária (a paleta do Filament ou um hexadecimal livre), logo da marca, favicon, a arte das telas de autenticação e o cabeçalho dos painéis (a marca composta e o bloco do usuário) Nome e versão; Cor primária; Logo, favicon e arte; Cabeçalho dos painéis
E-mail transporte (log, array, smtp), servidor, porta, criptografia, usuário, senha e remetente Remetente (transporte e remetente); Servidor SMTP (conexão e credenciais)
Tabelas linhas por página, linhas listradas, persistência do recorte do usuário, colunas arrastáveis, carregamento da tabela (paginação, rolagem infinita ou botão “Carregar mais”), visões salvas e o compartilhamento delas — os defaults de toda tabela dos três painéis (detalhes) Paginação e aparência; Carregamento e visões
Registro cadastro sem convite no /app, aprovação manual e validação de e-mail (detalhes) Cadastro sem convite (cadastro e aprovação); E-mail verificado (validação de e-mail)
Login a página única de login em /login (detalhes), os quatro provedores de login social, cada um com interruptor, painéis permitidos, Client ID e Client Secret (cifrado), além do rodapé da tela de login (detalhes) Página única de login; Login social (com uma seção por provedor); Proteção anti-robô; Rodapé da tela de login
Aplicação hub de navegação em cartões, aviso de alterações não salvas, densidade do layout, exibição da versão do kit no rodapé, dashboard dinâmico — e em quais painéis ele vale —, o dock de atalhos (painéis, layout padrão e estilo), os agentes de IA (o interruptor do uso e os painéis do chat do Assistente), o tutorial guiado (checklist, tours e autoria), como o seu negócio chama cada organização (singular e plural) e ocultar o seletor quando só uma está acessível Comportamento dos painéis; Dashboard dinâmico; Dock de atalhos; Agentes de IA; Tutorial guiado; Organizações

Tudo é gravado pelo spatie/laravel-settings na tabela settings, com a tela vindo do filament/spatie-laravel-settings-plugin — os dois já estavam instalados no kit e sem uso até esta versão.

O rodapé: assinatura para todos, versão só para quem entrou

O rodapé é montado em um só lugar (App\Support\AssinaturaDoRodape) e aparece em toda tela dos três painéis e nas telas de autenticação. O que muda entre elas não é o formato — é quem vê o quê:

Parte Visitante Quem entrou
© {ano} {Nome da aplicação} ✅ ✅
v{versão do sistema} ❌ ✅
kit {versão do kit} ❌ ✅, se o interruptor estiver ligado

A versão não aparece para quem não entrou, e isso é deliberado: exibir a versão exata da instalação na tela de login é entregar, a qualquer visitante, a lista de vulnerabilidades conhecidas que se aplicam a ela. O nome e o © são públicos de qualquer forma — já aparecem no topo do painel e no título da aba.

O ano é o corrente, calculado a cada render: não há campo para ele e ele muda sozinho na virada.

A versão do seu sistema sai do campo Versão do sistema, na aba Identidade, seção Nome e versão, semeado por APP_VERSION no .env. O campo mostra um v fixo à esquerda: ele é só visual, marca a convenção e não é gravado — digite 1.2.3, e o rodapé mostra v1.2.3. Se você digitar o v também, verá vv1.2.3, que é o jeito de a tela avisar que sobrou um.

São duas versões diferentes, e confundi-las é o erro que esta seção existe para evitar:

O que é Onde se edita
config('app.version') a versão do seu produto a tela, ou APP_VERSION no .env
config('kit.version') a versão do starter kit que originou o projeto ninguém: o kit:update a escreve sozinho

A segunda é métrica interna do kit — o kit:update a usa para saber a partir de qual versão comparar. Ela não aparece no rodapé por padrão; quem quiser vê-la ao lado da sua liga Mostrar também a versão do kit no rodapé, na aba Aplicação, seção Comportamento dos painéis. php artisan kit:info sempre mostra as duas, independentemente do interruptor.

Campo vazio, rodapé sem a SUA versão. Um projeto que não versiona não precisa fingir que versiona — a assinatura © {ano} {Nome} continua lá, só sem o número.

O recado da tela de login

A seção Rodapé da tela de login, na aba Login, tem um campo de texto livre que é renderizado dentro do cartão do formulário de login, abaixo dos botões (inclusive os de login social), e só nas telas de login (/admin/login, /app/login, /infra/login e /login). Ele aceita Markdown — negrito, itálico e link — e descarta HTML cru, porque a tela de login é pública.

As telas de registro e de recuperação de senha recebem só a assinatura, não o recado.

Se o interruptor estiver ligado e o campo vazio, o rodapé mostra a assinatura e, ao lado, a versão do kit rotulada (kit 0.52.0). Ela nunca é apresentada como se fosse a do seu produto: o rótulo é justamente o que impede essa leitura, e é requisito do kit, não detalhe de tela.

APP_VERSION semeia UMA vez, na instalação. Depois dela, quem manda é a tela.

É a mesma regra de toda chave desta página — o banco vence em tempo de execução; o .env semeia e é o plano B —, e aqui ela tem uma consequência que vale escrever por extenso: num projeto já instalado, editar APP_VERSION no .env não muda o rodapé. O valor gravado no banco vence, inclusive quando ele está vazio. Se o campo da tela está em branco, o rodapé fica sem a sua versão mesmo com APP_VERSION=2.4.1 no arquivo.

Então: APP_VERSION serve para a instalação nova nascer versionada. Para trocar a versão depois, o caminho é o campo na tela.

O kit não lê a tag nem o nome da branch do git em tempo de execução — imagem de produção normalmente não tem .git, e ler de lá criaria uma segunda fonte de verdade divergente do que a tela mostra. Um deploy que troca para a branch release/2.4 não atualiza o rodapé sozinho.

A versão nunca aparece para quem não entrou. O rodapé é renderizado também nas telas de login, registro e recuperação de senha, e ali ele fica vazio de propósito: versão exata de uma instalação é o mapa de vulnerabilidades aplicáveis a ela.

Avisar antes de perder o que foi digitado

Na aba Aplicação, seção Comportamento dos painéis, Avisar sobre alterações não salvas liga o alerta nativo do Filament: ao sair de um formulário com alteração pendente, o navegador pede confirmação antes de descartar.

Vale para toda tela de cadastro e edição dos três painéis, inclusive as que vêm de plugin de terceiro — quem decide é o painel, não o resource, então não há nada para colar tela a tela.

Nasce ligado: o comportamento anterior era perder o preenchimento em silêncio, e silêncio não é o padrão a preservar. Desligar é um clique, sem deploy.

É alerta, não rascunho: quem confirmar a saída perde o preenchimento do mesmo jeito. O kit avaliou dois pacotes de rascunho e salvamento automático e não adotou nenhum — os motivos estão em wikis/pacotes-candidatos.md.

Layout compacto: uma escala, não um interruptor

Ainda na aba Aplicação, seção Comportamento dos painéis, Densidade do layout aperta de uma vez os cartões de estatística, as tabelas, o menu lateral e os botões dos três painéis. São três degraus:

Nível --spacing Linha da tabela Tabela de 10 linhas Largura do menu
Confortável (padrão) .25rem, o do Filament 56,0 px 612 px 320 px
Compacto 0.2rem 46,4 px (−17,1%) 509,2 px (−16,8%) 272 px (−15,0%)
Denso 0.175rem 42,9 px (−23,4%) 471,8 px (−22,9%) 264 px (−17,5%)

Os números foram medidos no próprio kit, com navegador de verdade lendo estilo computado em /admin/users a 1600×1000 — não estimados.

Confortável (padrão) Compacto Denso
Densidade confortável Densidade compacta Densidade densa

As três são a mesma tela, e nascem da mesma suíte que prova que a opção funciona — não são montagem. Repare na coluna de ações à direita: no confortável ela é cortada em “Edi…”, e no denso cabe inteira. Apertar não é só ganhar altura; é deixar de esconder conteúdo.

Os três níveis de densidade, em sequência, na mesma tela

Nasce confortável, e de propósito: densidade é gosto, e atualizar o kit não deve mudar a aparência do seu projeto sozinho. No confortável o kit não emite estilo nenhum — o HTML é byte a byte o que sempre foi.

Vale no próximo F5. O kit emite uma única declaração CSS por requisição, num render hook do layout base do Filament: não há tema Vite, não há npm run build e não há deploy. Salvar na tela é suficiente.

O preço, e ele é visível no denso. O Filament 5 mede todo espaçamento a partir de uma variável só, então encolher a variável encolhe junto o que não é espaço: o ícone cai de 24 px para 19,2 px no compacto e 16,8 px no denso, e a caixa de texto passa a ser mais baixa que o botão ao lado dela (4 px de diferença no compacto, 6 px no denso). A caixa de seleção encolhe junto, e essa é a que pesa, porque é alvo de clique: o quadradinho do checkbox mede calc(var(--spacing) * 4), então vai de 16 px para 12,8 px no compacto e 11,2 px no denso. É exatamente por isso que a opção é uma escala e não um liga-desliga — quem achar a distorção incômoda, ou precisar do alvo de clique cheio, fica no degrau do meio, que entrega dois terços do ganho.

O menu aperta nos dois eixos, e a largura precisou de mecanismo próprio: ela vem de --sidebar-width, não de --spacing. As larguras da tabela acima são o mínimo medido — o rótulo mais longo do kit é “Configurações da aplicação”, e é ele que fixa o limiar. Se o seu projeto criar item de menu com rótulo mais longo, ele ganha reticências, que é o comportamento normal do Filament.

Três coisas não apertam, e nenhuma é defeito: a barra superior, que tem altura fixa; os cartões do /infra/pulse, que carregam CSS própria (as tabelas do Pulse apertam normalmente); e o rail do menu colapsado, deixado de fora de propósito — ele é só ícone, a largura dele é o alvo de clique, e o ícone dentro dele já encolhe. As três estão no roadmap do kit.

Existe um tema compacto oficial pago que chega a −22,5% sem distorcer ícone nem fonte. Ele não entra no kit porque a licença é de projeto único e um starter kit é distribuído — o estudo com os números está na wiki layout-compact. O degrau denso chega praticamente ao mesmo ganho, com a distorção como contrapartida.

O dashboard dinâmico é um interruptor, não uma migração

Ainda na aba Aplicação, a seção Dashboard dinâmico troca a tela de entrada dos painéis: em vez do dashboard clássico, uma grade que o próprio usuário monta, move e redimensiona (mddev31/filament-dynamic-dashboard). Nasce desligada — KIT_DASHBOARD_DINAMICO=false no .env.example —, e Painéis onde vale restringe a quais painéis: em branco significa todos.

O que faz dela um interruptor, e não um caminho sem volta, são duas decisões:

  • As duas páginas estão sempre registradas. O painel é montado no register() do provider e o valor do banco só chega no boot(); um ->pages([...]) condicional leria a config antes de ela existir. Quem decide qual página atende é App\Support\DashboardDinamico, por request — salvar aqui vale no próximo F5, sem cache nem restart.
  • Desligar não apaga nada. As grades montadas ficam nas tabelas dashboards e dashboard_widgets e voltam intactas ao religar.

Ver não é montar: quem arrasta e salva a grade é quem tem a permissão Manage:Dashboard do Shield — os demais veem a mesma tela sem poder editá-la.

O dock de atalhos: três opções, nenhum painel de partida

Ainda na aba Aplicação, a seção Dock de atalhos configura o botão flutuante com os favoritos de cada usuário (como ele funciona):

Opção O que define Nasce
Painéis onde o dock aparece os painéis (admin, app, infra) em que o dock aparece nenhum
Layout padrão Leque (speed dial) ou Barra; cada usuário pode trocar o dele Leque (speed dial)
Estilo Nativo, Contorno, Contraste ou Tingido; vale para todos, o usuário não o troca Nativo

A lista de painéis nasce da variável KIT_DOCK_PAINEIS (ids separados por vírgula, em config/kit.php → dock.paineis), vazia no .env.example. Vazia significa nenhum painel — o oposto do Painéis onde vale do dashboard dinâmico, logo acima, em que em branco significa todos. Layout e estilo não têm variável: quem os muda é esta tela. Como no restante da aba, o banco vence o .env e salvar vale no próximo F5. Quem vê o dock continua sendo definido pela permissão Usar:Dock, não por esta seção.

Os agentes de IA: um interruptor e os painéis do chat

Ainda na aba Aplicação, a seção Agentes de IA liga a camada de IA do kit e decide onde o chat do Assistente aparece:

Opção O que define Nasce
Usar agentes de IA ligado, o catálogo Agentes de IA volta ao menu do /admin e o chat pode aparecer; desligado, o catálogo some do menu, do hub, da busca ⌘K e do dock, a URL /admin/agentes-ia responde 404 (mesmo para quem tem a permissão), o grupo de widgets Agentes de IA some do dashboard do /admin e o chat não aparece em painel nenhum desligado
Painéis onde o chat do Assistente aparece os painéis (admin, app, infra) em que o botão flutuante do chat é montado; só vale com o interruptor ligado nenhum

Os dois nascem das variáveis KIT_AGENTES_IA e KIT_AGENTES_IA_PAINEIS (ids separados por vírgula, em config/kit.php → agentes_ia), desligado e vazia no .env.example. Vazia significa nenhum painel, como no dock. Como no restante da aba, o banco vence o .env e salvar vale no próximo F5: nada é consultado no banco para decidir, e nada é apagado ao desligar — os agentes, as conversas e os favoritos continuam gravados e voltam ao religar. Até a v0.50.0 o chat era fixo no /app; agora ele pode estar em qualquer painel, e um chat já aberto quando o uso é desligado recusa a próxima mensagem com 404. Quem abre o catálogo continua sendo definido pela permissão ViewAny:AgenteIa: o interruptor é uma condição a mais, não substitui a permissão.

O tutorial guiado: semear e ligar são escolhas separadas

Ainda na aba Aplicação, a seção Tutorial guiado liga as jornadas de primeiros passos nos três painéis (como funciona):

Opção O que define Nasce
Usar o tutorial guiado ligado, mostra o checklist flutuante, os tours, a página Primeiros passos e a autoria; desligado, os menus Onboarding e Condições somem do /admin e suas URLs respondem 404 (mesmo para quem tem a permissão), o widget Jornadas de onboarding concluídas some do dashboard do /admin e o checklist, os tours e a página Primeiros passos não aparecem em painel nenhum desligado

O interruptor nasce da variável KIT_ONBOARDING (config/kit.php → onboarding.habilitado), com false no .env.example. Como no restante da aba, o banco vence o .env. Não há lista de painéis: cada jornada declara o seu. Nada é apagado ao desligar; ao religar, o progresso volta. Um checklist já aberto recusa a próxima ação enquanto o uso estiver desligado, sem gravar nada.

Semear não liga o interruptor. Rode php artisan db:seed --class=OnboardingSeeder (ou use kit:install --onboarding na instalação) para criar o conteúdo e ligue o uso nesta tela. As duas coisas podem ser feitas em qualquer ordem. Quem vê o tutorial continua precisando da permissão View:PrimeirosPassos; a autoria segue ViewAny:OnboardingFlow e ViewAny:OnboardingCondition.

Um seletor para quem não tem o que trocar

Com a multi-organização ligada, a seção Organizações da aba Aplicação tem Ocultar o seletor quando houver uma organização só. Ligado, o bloco inteiro do topo da barra lateral do /app — avatar, rótulo e nome — some para quem tem acesso a uma organização ativa; sem troca possível, ele só repete o nome que o cabeçalho já mostra. Com duas ou mais, o seletor continua lá.

Quem conta é a mesma lista que alimenta o seletor: organizações ativas do usuário — e, para o master_global, todas as ativas. Vínculo com organização inativa não conta.

É só aparência: canAccessTenant(), o endereço /app/{tenant} e o link direto não mudam. Nasce desligado e vale no próximo F5 — a decisão é lida por request, não no boot do painel.

O seletor visível e oculto, na mesma tela

O interruptor na aba Aplicação
O interruptor “ocultar o seletor” na aba Aplicação

Quem manda: o banco ou o .env?

Esta é a pergunta que decide se a tela é útil ou decorativa, e a resposta é uma só:

O banco vence em tempo de execução. O .env semeia a primeira gravação e é o plano B.

flowchart LR
%% DG-14
accTitle: De onde vem a configuração
accDescr: O .env semeia config/*.php no boot; o banco sobrepõe as chaves do mapaDeConfiguracao quando a tabela settings existe, e a tela de configurações grava nele; chave fora do mapa, como KIT_TENANCY, só tem o .env como fonte.
  env_file[".env"] --> config_php["config/*.php"]
  settings_banco["Settings no banco"] -->|"sobrepõe, chaves do mapa"| config_php
  tela_config["Tela de configurações"] -->|"grava"| settings_banco
  env_file -.->|"só .env: KIT_TENANCY e demais fora do mapa"| chave_fora_do_mapa["Chave fora do mapaDeConfiguracao"]

ConfiguracoesDoKit::aplicarNaConfig() é quem executa a segunda seta (app/Settings/ConfiguracoesDoKit.php:aplicarNaConfig:640), chamado no boot por KitServiceProvider::configureSettingsDoKit() (app/Providers/KitServiceProvider.php:configureSettingsDoKit:467); as chaves sobrepostas são exatamente as de mapaDeConfiguracao() (app/Settings/ConfiguracoesDoKit.php:mapaDeConfiguracao:469) — KIT_TENANCY (config/kit.php:KIT_TENANCY:414) não está nesse mapa, então o banco nunca a sobrescreve.

Como isso funciona sem que nenhum consumidor saiba que o settings existe:

  1. A migration database/settings/*_create_kit_settings.php semeia cada propriedade com o valor de config(...), que vem do .env. Numa instalação nova, a cor e o nome que você escolheu no kit:install chegam ao banco sozinhos — o migrate roda depois de o instalador ter escrito o arquivo.
  2. App\Providers\KitServiceProvider::configureSettingsDoKit() sobrepõe a configuração do processo com o que está no banco, uma vez por request e por comando artisan.
  3. App\Support\CorPrimaria, os três PanelProvider, a configuração global de tabela e o próprio MailManager do Laravel continuam lendo config(). Nenhum deles foi alterado.

O que acontece em cada situação:

Situação Quem vence
a propriedade tem linha no banco o banco
a propriedade não tem linha (você acrescentou uma e não migrou) o .env, com um warning no log
a tabela settings não existe (antes do primeiro migrate) o .env, em silêncio
o banco está inacessível o .env, com um warning
kit:install numa instalação nova o .env → a migration leva os valores para o banco
kit:install --force apaga o banco, reescreve o .env e re-migra → o banco nasce igual ao .env novo
kit:install --custom num projeto já instalado reescreve o .env e grava no settings — as duas fontes ficam iguais

Não existe interruptor para “usar ou não o settings”, e isso é decisão, não esquecimento: uma flag seria uma terceira fonte da verdade, que é justamente o problema que a regra acima resolve. Para desligar, php artisan migrate:rollback na migration de settings — sem linha na tabela, o alinhamento é no-op e o .env volta a ser a única fonte.

Cor: lista fechada e cor livre

Na seção Cor primária da aba Identidade, são dois campos, e a precedência é declarada:

hexadecimal válido → nome da paleta → padrão do Filament.

O hexadecimal vence porque é o campo mais específico: quem digita #7c3aed escolheu aquela cor, enquanto o seletor da lista tem valor padrão e pode nunca ter sido tocado. Valor fora do formato (#abcd, azul, #gggggg) é ignorado e a resolução cai para o nome — a mesma tolerância que o kit já tinha para nome de cor inválido, e pelo mesmo motivo: isto roda no boot de todo painel, e uma exceção ali derrubaria toda página do projeto, não uma tela.

Dentro de /app/{organização}, a cor da organização continua vencendo as duas.

Logo da marca: uma só, ou duas por tema

A seção Logo, favicon e arte da aba Identidade tem um interruptor, Uma logo só, nos dois temas — e ele nasce LIGADO, porque instalação existente não muda de cara com update do kit. Com ele ligado, a mesma logo vale no tema claro e no escuro, como sempre foi.

Desligado, a marca separa em duas imagens:

  • Logo da marca — exibida no tema claro, e na marca do topo dos três painéis. Fundo claro recomendado.
  • Logo para o tema escuro — exibida só no tema escuro. Fundo transparente recomendado: com fundo branco, a logo vira um bloco sólido no escuro.

Os dois campos viram obrigatórios entre si no modo separado: gravar a clara sem a escura (ou o contrário) é recusado. Com os dois vazios, o painel volta ao nome em texto, sem erro.

A logo do tema claro na tela de bloqueio

A logo do tema escuro na tela de bloqueio

Quem troca a imagem é o navegador, pela classe dark do <html> — o servidor não conhece o tema quando ele é “Sistema”, então as duas <img> saem juntas no HTML e o CSS do Filament exibe a certa. O topo do painel do negócio e a tela de bloqueio usam o mesmo par, conforme os interruptores da instalação e da organização.

A organização tem o mesmo interruptor, no formulário dela: Uma logo só, nos dois temas. Ligado (o padrão), a logo enviada cobre o claro e o escuro e a empresa não precisa mandar uma segunda imagem; desligado, o campo da variante escura aparece. Quem já tinha logo_dark gravada quando o recurso chegou nasce com o interruptor desligado — a separação existente não é desfeita por update.

Com unifica_logo ligado e a logo clara própria disponível, ela serve os dois temas; uma variante escura antiga da organização não é usada. Se a clara não foi enviada ou seu arquivo não existe mais, usa-se o par completo da instalação, respeitando o interruptor global. Com a unificação da organização desligada, o fallback é por variante: a clara própria cai para a clara da instalação; a escura própria cai para a escura da instalação e, sem esta, o navegador exibe a clara resolvida. Essa variante escura só vale com a unificação global também desligada; com a global ligada, a clara resolvida serve os dois temas, independentemente do interruptor da organização.

Cabeçalho dos painéis: o que mostrar no topo

A aba Identidade fecha com a seção Cabeçalho dos painéis: quatro interruptores e um seletor, todos nascidos desligados — com nada ligado, os três painéis ficam exatamente como sempre foram (a logo enviada ou, sem ela, o nome em texto). É a mesma regra da logo por tema: instalação existente não muda de cara com update do kit.

Os três primeiros compõem a marca do topo, nesta ordem, com um separador entre cada segmento presente:

  • Nome do projeto na marca do topo — o nome da aplicação, em texto.
  • Nome do painel na marca do topo — Administração, Infraestrutura ou, no painel do negócio, o nome da organização aberta. Sem organização aberta, o painel do negócio omite o segmento quando ele repetiria o nome do projeto ao lado (lá o rótulo do painel é o nome da aplicação).
  • Logo da marca ao fim da composição — a logo da seção anterior (a clara e, com a marca separada, a escura, trocadas pelo navegador). Sem logo enviada, nada aparece neste lugar — e se nenhum segmento resolver, a marca volta à de sempre, nunca fica vazia.

A composição substitui a marca onde quer que o Filament a desenhe: no topbar e, em tela estreita com a barra lateral aberta, no topo dela (ali os textos encolhem e cortam com reticências para caber numa linha). As telas de autenticação (login, recuperação de senha, dois fatores, confirmação de e-mail) continuam com a marca de sempre: o cabeçalho é das telas internas dos painéis. No painel do negócio (/app), com uma organização aberta, a logo da composição (e a marca simples, com a composição desligada) é a da organização, com unificação e fallback conforme a seção anterior; o alt da imagem é o nome da organização quando a logo é dela. Nos painéis de administração e infraestrutura, a logo é a da instalação; nas telas de autenticação (login, recuperação de senha) também, e a tela de bloqueio usa a organização da sessão, pela mesma regra.

O quarto interruptor, Nome do usuário ao lado do avatar, mostra quem está autenticado à esquerda do avatar, com uma linha abaixo escolhida em Abaixo do nome do usuário: o perfil (o papel no painel corrente, com o mesmo rótulo do menu do avatar — Administrador Geral em qualquer painel; quem não tem papel no painel fica só com o nome) ou o e-mail. O bloco some abaixo de 768 px de largura; o menu do avatar continua mostrando nome, e-mail e papel. O seletor só aparece com o bloco ligado.

As cinco opções têm par no .env, que semeia a primeira gravação e é o plano B — como todas as outras desta tela (Quem manda):

# KIT_CABECALHO_NOME_DO_PROJETO=false
# KIT_CABECALHO_NOME_DO_PAINEL=false
# KIT_CABECALHO_LOGO_DA_MARCA=false
# KIT_CABECALHO_USUARIO=false
# KIT_CABECALHO_DETALHE_DO_USUARIO=perfil # ou email; fora da lista cai em perfil

Permissão

Uma só: View:ConfiguracoesDoKit, gerada pelo ShieldPermissionsSeeder e entregue ao papel admin pelo PapeisSeeder — sem nenhuma lista para editar, porque a matriz do papel é a do painel inteiro. master_global entra pelo Gate::before; infra e panel_user não recebem.

É uma permissão para abrir e para salvar, de propósito. O canEdit() do plugin desabilita o formulário mas não esconde valor — o próprio README do pacote diz isso por escrito —, e esta tela guarda a senha do SMTP. Um papel “só leitura” aqui seria um papel que lê credencial.

Teto de upload: 10 MB, e onde mudar

Todo upload do kit — a logo, o favicon e a arte do login desta tela, a logo da organização em /admin/organizacoes e os anexos de Projeto — aceita arquivo de até 10 MB, e recusa SVG.

O número é uma chave, no .env:

# Em MEGABYTES. Vazio, 0 ou ausente = 10.
KIT_UPLOAD_MAXIMO_MB=10

Ela alimenta config('kit.uploads.maximo_em_kb') — a config guarda kilobytes, porque é a unidade que o ->maxSize() do Filament e a regra de upload temporário do Livewire recebem. A multiplicação por 1024 vive num lugar só, no config/kit.php, e quem lê a chave é App\Support\TetoDeUpload. Não há campo na tela para isto de propósito: é decisão de instalação, não de operação diária.

Um upload atravessa quatro limites, e o menor manda. Eles não recusam igual, e é isso que torna o desalinhamento caro:

Camada Onde Valor no kit Como aparece o erro
nginx docker/nginx/nginx.conf client_max_body_size 60M falha de rede no console
PHP docker/php/uploads.ini upload_max_filesize=52M, post_max_size=60M idem
Livewire (upload temporário) alinhado à chave do kit por KitServiceProvider, com 1 MB de folga 11 MB 422 no XHR, erro genérico
Filament (->maxSize()) a chave do kit 10 MB mensagem em português, no campo

Só a última recusa com mensagem clara — por isso o kit alinha o Livewire à chave em vez de deixar o default dele (12 MB) mais frouxo que a tela.

Para subir muito o teto, mude junto:

  1. KIT_UPLOAD_MAXIMO_MB — cobre a tela e o Livewire de uma vez;
  2. acima de 52 MB, docker/php/uploads.ini (upload_max_filesize e post_max_size);
  3. acima de 60 MB, docker/nginx/nginx.conf (client_max_body_size).

⚠️ Fora do Docker do kit, o PHP costuma vir com upload_max_filesize=2M de fábrica. Ali o teto real é 2 MB, não o da chave — e o erro aparece como falha de rede, sem mencionar tamanho. Confira com php -i | grep upload_max_filesize antes de culpar o kit.

Por que SVG é recusado

SVG é XML, e XML aceita <script>. A logo, o favicon e a arte do login são servidos pelo mesmo origin da aplicação, com visibilidade pública: abrir a URL de um SVG enviado executaria o script com acesso ao cookie de sessão — XSS armazenado. Quem envia é o admin, que já tem acesso total, então é escalada de insider e não porta anônima; num starter kit vale fechar.

A barreira é a regra mimes do Laravel (não o ->image() do Filament, que é outra coisa e aceita image/*, SVG incluído), com a lista de formatos em ConfiguracoesDoKit::FORMATOS_DE_IMAGEM: jpg, jpeg, png, gif, bmp, webp, avif, heic, heif, ico, tif e tiff. SVG é o único formato de imagem fora, e é o único que carrega script.

E ela não olha a extensão: o MIME vem do conteúdo do arquivo no disco temporário, então renomear logo.svg para logo.png não passa. Nos anexos de Projeto, onde uma allow-list fecharia o campo para PDF e planilha, a regra recusa apenas image/svg+xml.

Trilha de alterações

Toda alteração aparece em /infra/audits, com quem mudou, quando, o nome da propriedade e os valores antigo e novo. Uma linha por propriedade alterada; salvar sem mudar nada não gera registro.

A senha de e-mail é cifrada na tabela settings e entra na trilha mascarada (••••••): o registro diz que o segredo mudou, nunca qual é.

Dois detalhes que valem para quem for mexer nisso:

  • A trilha não vem da trait App\Traits\AuditsFillables. Um settings do spatie não é um model Eloquent, e apontar o repositório dele para um model com a trait auditaria só a criação — a alteração de propriedade existente passa por upsert(), que não dispara evento de Eloquent. A trilha sai de um listener de SavingSettings, que é o único ponto do pacote com valor antigo e novo juntos.
  • O evento gravado é settings-updated, e não updated, para o botão “restaurar” da trilha não aparecer: ele faria fill(['nome_da_aplicacao' => …]) numa linha cujas colunas são group/name/payload.

Isto não é o settings de uma organização

A identidade visual de um tenant (cor e logo por organização) continua sendo CRUD comum em /admin/organizacoes, nas colunas cor_primaria (hexadecimal livre), cor_primaria_nome (a mesma paleta do Filament deste settings — o hexadecimal vence quando preenchido) e logo do model Tenant, e ela vence a do kit dentro de /app/{slug}. Nada foi movido para cá.

O que ficou fora, e por quê

Item Por quê
driver, host e nome do banco trocar depois do migrate não é reescrita de configuração, é outra instalação
ligar/desligar a multi-organização as tabelas de permissão só nascem com a coluna de contexto se permission.teams estiver ativo antes do migrate; o caminho é php artisan kit:tenancy
e-mail e senha do administrador o UsuarioAdminSeeder não sincroniza, de propósito (ele roda em todo db:seed, e atualizar senha ali reverteria em silêncio a troca feita no perfil). Um campo que não troca a credencial é pior que campo nenhum — o caminho é a tela de perfil
slug do CRUD de organizações é lido no registro de rota, não no render, e a URL é identificador permanente
idiomas do painel a internacionalização do kit não está feita: ligar um segundo idioma hoje troca metade da tela. Ver o bloco idiomas de config/kit.php
retenção das trilhas não é pergunta da instalação; fica no .env, onde o zero tem semântica documentada

Desempenho

O alinhamento custa uma query por boot (o grupo inteiro vem de uma só leitura). Se isso incomodar, SETTINGS_CACHE_ENABLED=true no .env — lembrando que, com o cache ligado, gravar pela tela exige php artisan settings:clear-cache.

Acrescentando uma propriedade

Três lugares, sempre, e o teste tests/Kit/ConfiguracoesDoKitTest.php reprova se você esquecer um:

  1. a propriedade tipada em app/Settings/ConfiguracoesDoKit.php;
  2. a linha em ConfiguracoesDoKit::mapaDeConfiguracao() (propriedade → chave de config());
  3. o par add() / deleteIfExists() numa migration nova em database/settings/.

E o campo na aba certa de app/Filament/Admin/Pages/ConfiguracoesDoKit.php.