Arquitetura De Settings
O MangoStudio tem três camadas independentes de settings, cada uma com sua própria persistência, API e módulo no frontend.
Taxonomia De Settings
| Camada | O que controla | Tabela de persistência | Escopo |
|---|---|---|---|
| App Settings | Tema, idioma, defaults de geração, configuração de prompt, contexto | user_app_settings |
Um blob por usuário |
| Provider Settings | Configuração de runtime por provedor | user_provider_settings |
Um blob por usuário+provedor |
| Tool Settings | Enable/disable de tools e overrides de parâmetros | user_tool_settings |
Uma linha por usuário+tool |
App Settings
Controlam preferências globais que valem para todos os provedores e tools.
Campos armazenados:
| Campo | Tipo | Padrão |
|---|---|---|
promptSettings |
PromptSettings |
Prompts padrão do sistema |
globalImageQuality |
'512px' | '1K' | '2K' | '4K' |
'1K' |
thinkingEnabled |
boolean |
true |
reasoningEffort |
ReasoningEffort |
'medium' |
maxToolIterations |
number (1–1000) |
1000 |
contextSettings |
ContextSettings |
Defaults de compactação |
API:
GET /api/settings/app → Retorna AppSettingsResponse (AppSettings + libraryLocationDefaultsPending)
PUT /api/settings/app → Mescla um patch e retorna AppSettingsResponse normalizado
Quais locais da biblioteca vêm ligados por padrão é detectado a partir das CLIs
de agente instaladas na máquina do hub, e a primeira detecção após o hub iniciar
pode levar segundos. O GET nunca espera por ela: enquanto não existe um valor
detectado, responde as configurações salvas sobre defaults provisórios estáticos
com libraryLocationDefaultsPending: true e, quando a detecção termina, publica
uma invalidação app de configurações para cada usuário que recebeu essa
resposta. Clientes não devem exibir nem regravar a habilitação de locais da
biblioteca enquanto a flag estiver ligada. O PUT sempre espera a detecção,
então sua resposta nunca é provisória e uma escrita nunca grava o placeholder.
O corpo do PUT é um patch, não um snapshot. Todo campo é opcional; o servidor sobrepõe o que chega ao registro que já tem e depois normaliza. Duas consequências, e ambas são o objetivo: um cliente sobrevive a todo campo que o schema ganhar depois, em vez de ser recusado por omitir um que nunca conheceu, e duas telas editando configurações diferentes param de sobrescrever uma à outra porque nenhuma carrega o campo da outra.
A regra, por inteiro:
- uma chave omitida mantém o que está guardado;
- um campo enviado substitui por inteiro o que estava guardado nele. Um membro opcional que o patch deixa de fora é uma limpeza, não uma omissão — é assim que “a máquina mudou, esqueça aquela pasta” se diz;
- arrays substituem pelo mesmo motivo — mesclar por índice tornaria “remova a segunda regra” inexprimível;
- só os contêineres acima desses campos mesclam:
profileSettingse o registro de um perfil, cujos filhos têm donos diferentes; nullem uma chave que o aceita reseta aquela subárvore para o padrão. É assim que uma subárvore inteira é limpa, e funciona porque a normalização roda depois da mesclagem.
A unidade de um patch é um campo de topo inteiro: envie contextSettings e você
envia tudo dele, e meia subárvore é recusada em vez de completada com padrões.
Um campo só é endereçável mais fundo onde suas partes têm donos independentes —
profileSettings.<id>.libraryLocations e .onboarding são escritos por telas
diferentes, então cada um é opcional por conta própria.
As settings ficam em um único blob JSON na coluna settingsJson. Na leitura, normalizeAppSettings() trata dados parciais ou inválidos, caindo para defaults em qualquer campo ausente ou incorreto.
Prompt Settings (subobjeto)
Controlam prompts de sistema e configuração de arquivos de regra:
textSystemPrompt— prompt de sistema para geração de textoimageSystemPrompt— prompt de sistema para geração de imagemagentsMd— conteúdo deAGENTS.mdusado como injeção no promptclaudeMd— conteúdo deCLAUDE.mdusado como injeção no promptcustomRules— arquivos de regra definidos pelo usuário com path, estado habilitado, papel de injeção e frequência de envio
Context Settings (subobjeto)
Controlam o comportamento da janela de contexto:
compactionEnabled/summarizationEnabled— habilitam ou desabilitam recursos de compactaçãocompactionThreshold— percentual a partir do qual a compactação é oferecida; o padrão é 85compactionSummaryModelId— modelo usado para sumarização
Provider Settings
Controlam o comportamento de runtime por provedor. Cada provedor possui uma policy que define quais settings se aplicam.
API:
GET /api/settings/providers → Lista todos os descritores de provedor
GET /api/settings/providers/:provider → Um descritor específico
PUT /api/settings/providers/:provider → Atualiza provider settings
Policy De Provider Settings
Cada tipo de provedor tem uma policy hardcoded em provider-settings-policy.ts:
| Provedor | Thinking | Prompt caching | Tools | Structured output |
|---|---|---|---|---|
| Gemini | Sim | Não | Sim | Limitado |
| OpenAI | Sim | Não | Sim | Sim |
| OpenAI-compatible | Depende | Depende | Sim | Depende |
| Anthropic | Sim | Sim | Sim | Não |
| DeepSeek | Sim | Sim | Sim (apenas v4) | Não |
Os settings são normalizados contra a policy do provedor em toda leitura e escrita. Por exemplo, Anthropic sempre retorna structuredOutputSupported: false independentemente do dado salvo.
Shape Das Runtime Settings
interface ProviderRuntimeSettings {
thinkingEnabled?: boolean;
reasoningEffort?: 'low' | 'medium' | 'high' | 'xhigh' | 'max';
maxOutputTokens?: number;
maxToolIterations?: number;
promptCachePreference?: 'auto' | 'enabled' | 'disabled';
}
Tool Settings
Controlam quais tools ficam habilitadas e quais valores de parâmetro cada usuário usa.
API:
GET /api/settings/tools → Lista todos os descritores de tool com settings
PUT /api/settings/tools/:toolName → Atualiza os settings de uma tool
Cada linha de tool tem uma coluna dedicada enabled como inteiro 0/1 e um blob parametersJson. Os settings são validados contra parameterDescriptors da tool no momento da escrita; valores inválidos retornam HTTP 422.
Parameter Descriptors
Cada tool declara seus parâmetros configuráveis:
interface ToolParameterDescriptor {
key: string;
type: 'string' | 'number' | 'boolean' | 'select';
label: string;
description: string;
required: boolean;
defaultValue: unknown;
options?: Array<{ label: string; value: unknown }>;
min?: number;
max?: number;
modelType?: 'image'; // para seletores de modelo alimentados por catálogo
}
Fluxo De Settings
Componente do frontend
│
▼
Hook (TanStack Query: useQuery / useMutation)
│
▼
Eden Treaty Client (chamadas de API tipadas)
│
▼
Rota Elysia (auth + validação TypeBox)
│
▼
Application Service
├─ validação (ownership, restrições da policy)
├─ normalização (merge com defaults, clamp de valores)
├─ repository (upsert com Kysely)
│
▼
Resposta (tipada via shared contracts)
Todas as queries de settings usam staleTime de 30 segundos no TanStack Query. Mutations invalidam automaticamente suas listas correspondentes em caso de sucesso.
Migração: localStorage → Banco De Dados
Originalmente, app settings eram persistidos em localStorage do browser via useGlobalSettings(). O PR #210 migrou isso para uma API com persistência em banco:
- Contratos compartilhados — novo módulo
app-settingscom schemas TypeBox e defaults. - Módulo da API — rotas HTTP de
app-settings+ application service + repository + migração 018. - Migração do frontend —
useGlobalSettings()foi refatorado de leitura/escrita emlocalStoragepara TanStack Query usando a API.
Provider settings e tool settings sempre foram persistidos em banco desde sua introdução.
Adicionando Um Novo Setting
- Defina o tipo e o schema em
apps/shared/src/<module>/. - Adicione o campo ao contrato compartilhado e aos defaults.
- Atualize o repository da API para ler/escrever o novo campo.
- Atualize a normalização do application service para tratar dados parciais.
- Adicione a rota da API, se necessário.
- Adicione o hook do frontend e o componente de UI.
- Se o setting afetar comportamento no momento da geração, conecte-o ao serviço apropriado, como
stream-text-turn.tslendogetAppSettings()ougetEnabledToolRuntime().