Get started
Design de Importação de Tema Personalizado do Tweakcn
Design de Importação de Tema Personalizado do Tweakcn
Status: aprovado no terminal em 2026-04-22
Resumo
Adicione exatamente um slot personalizado local ao navegador para tema da Interface de Controle que possa ser importado a partir de um link de compartilhamento do tweakcn. As famílias de temas integradas existentes continuam sendo claw, knot e dash. A nova família custom se comporta como uma família de temas normal do OpenClaw e oferece suporte aos modos light, dark e system quando o payload importado do tweakcn inclui conjuntos de tokens claros e escuros.
O tema importado é armazenado apenas no perfil atual do navegador com o restante das configurações da Interface de Controle. Ele não é gravado na configuração do gateway e não é sincronizado entre dispositivos ou navegadores.
Problema
O sistema de temas da Interface de Controle atualmente está fechado sobre três famílias de temas codificadas de forma rígida:
ui/src/ui/theme.tsui/src/ui/views/config.tsui/src/styles/base.css
Os usuários podem alternar entre famílias integradas e variantes de modo, mas não conseguem trazer um tema do tweakcn sem editar o CSS do repositório. O resultado solicitado é menor do que um sistema geral de temas: manter os três temas integrados e adicionar um slot importado controlado pelo usuário que possa ser substituído por um link do tweakcn.
Objetivos
- Manter as famílias de temas integradas existentes inalteradas.
- Adicionar exatamente um slot personalizado importado, não uma biblioteca de temas.
- Aceitar um link de compartilhamento do tweakcn ou uma URL direta
https://tweakcn.com/r/themes/{id}. - Persistir o tema importado apenas no armazenamento local do navegador.
- Fazer o slot importado funcionar com os controles de modo
light,darkesystemexistentes. - Manter o comportamento de falha seguro: uma importação incorreta nunca quebra o tema ativo da interface.
Não objetivos
- Nenhuma biblioteca multitema ou lista local do navegador de importações.
- Nenhuma persistência no Gateway ou sincronização entre dispositivos.
- Nenhum editor arbitrário de CSS ou editor de JSON bruto de tema.
- Nenhum carregamento automático de ativos de fonte remotos do tweakcn.
- Nenhuma tentativa de oferecer suporte a payloads do tweakcn que expõem apenas um modo.
- Nenhuma refatoração de temas em todo o repositório além das interfaces necessárias para a Interface de Controle.
Decisões do usuário já tomadas
- Manter os três temas integrados.
- Adicionar um slot de importação alimentado pelo tweakcn.
- Armazenar o tema importado no navegador, não na configuração do Gateway.
- Oferecer suporte a
light,darkesystempara o slot importado. - Sobrescrever o slot personalizado com a próxima importação é o comportamento pretendido.
Abordagem recomendada
Adicione um quarto id de família de tema, custom, ao modelo de temas da Interface de Controle. A família custom se torna selecionável apenas quando uma importação válida do tweakcn está presente. O payload importado é normalizado em um registro de tema personalizado específico do OpenClaw e armazenado no armazenamento local do navegador com o restante das configurações da interface.
Em tempo de execução, o OpenClaw renderiza uma tag <style> gerenciada que define os blocos resolvidos de variáveis CSS personalizadas:
:root[data-theme="custom"] { ... }
:root[data-theme="custom-light"] { ... }
Isso mantém as variáveis do tema personalizado com escopo restrito à família custom e evita vazar variáveis CSS inline para as famílias integradas.
Arquitetura
Modelo de tema
Atualize ui/src/ui/theme.ts:
- Estenda
ThemeNamepara incluircustom. - Estenda
ResolvedThemepara incluircustomecustom-light. - Estenda
VALID_THEME_NAMES. - Atualize
resolveTheme()para quecustomespelhe o comportamento da família existente:custom + dark->customcustom + light->custom-lightcustom + system->customoucustom-lightcom base na preferência do SO
Nenhum alias legado é adicionado para custom.
Modelo de persistência
Estenda a persistência de UiSettings em ui/src/ui/storage.ts com um payload opcional de tema personalizado:
customTheme?: ImportedCustomTheme
Formato armazenado recomendado:
type ImportedCustomTheme = {
sourceUrl: string;
themeId: string;
label: string;
importedAt: string;
light: Record<string, string>;
dark: Record<string, string>;
};
Observações:
sourceUrlarmazena a entrada original do usuário após a normalização.themeIdé o id do tema do tweakcn extraído da URL.labelé o camponamedo tweakcn quando presente; caso contrário,Custom.lightedarkjá são mapas de tokens normalizados do OpenClaw, não payloads brutos do tweakcn.- O payload importado fica ao lado de outras configurações locais do navegador e é serializado no mesmo documento de armazenamento local.
- Se os dados armazenados do tema personalizado estiverem ausentes ou forem inválidos no carregamento, ignore o payload e volte para
theme: "claw"quando a família persistida forcustom.
Aplicação em tempo de execução
Adicione um gerenciador estreito de folha de estilos de tema personalizado no runtime da Interface de Controle, mantido perto de ui/src/ui/app-settings.ts e ui/src/ui/theme.ts.
Responsabilidades:
- Criar ou atualizar uma tag
<style id="openclaw-custom-theme">estável emdocument.head. - Emitir CSS apenas quando existir um payload de tema personalizado válido.
- Remover o conteúdo da tag de estilo quando o payload for limpo.
- Manter o CSS das famílias integradas em
ui/src/styles/base.css; não emendar tokens importados na folha de estilos versionada.
Esse gerenciador roda sempre que as configurações são carregadas, salvas, importadas ou limpas.
Seletores de modo claro
A implementação deve preferir data-theme-mode="light" para estilização clara entre famílias, em vez de tratar custom-light como caso especial. Se um seletor existente estiver fixado em data-theme="light" e precisar se aplicar a todas as famílias claras, amplie-o como parte deste trabalho.
UX de importação
Atualize ui/src/ui/views/config.ts na seção Appearance:
- Adicione um cartão de tema
Customao lado deClaw,KnoteDash. - Mostre o cartão como desabilitado quando nenhum tema personalizado importado existir.
- Adicione um painel de importação abaixo da grade de temas com:
- uma entrada de texto para um link de compartilhamento do tweakcn ou URL
/r/themes/{id} - um botão
Import - um caminho
Replacequando um payload personalizado já existir - uma ação
Clearquando um payload personalizado já existir
- uma entrada de texto para um link de compartilhamento do tweakcn ou URL
- Mostre o rótulo do tema importado e o host de origem quando um payload existir.
- Se o tema ativo for
custom, importar uma substituição aplica imediatamente. - Se o tema ativo não for
custom, a importação apenas armazena o novo payload até que o usuário selecione o cartãoCustom.
O seletor rápido de temas em ui/src/ui/views/config-quick.ts também deve mostrar Custom apenas quando existir um payload.
Análise de URL e busca remota
O caminho de importação do navegador aceita:
https://tweakcn.com/themes/{id}https://tweakcn.com/r/themes/{id}
A implementação deve normalizar os dois formatos para:
https://tweakcn.com/r/themes/{id}
Então o navegador busca diretamente o endpoint /r/themes/{id} normalizado.
Use um validador de esquema estreito para o payload externo. Um esquema zod é preferido porque este é um limite externo não confiável.
Campos remotos obrigatórios:
namede nível superior como string opcionalcssVars.themecomo objeto opcionalcssVars.lightcomo objetocssVars.darkcomo objeto
Se cssVars.light ou cssVars.dark estiver ausente, rejeite a importação. Isso é deliberado: o comportamento de produto aprovado é suporte completo a modos, não síntese de melhor esforço do lado ausente.
Mapeamento de tokens
Não espelhe variáveis do tweakcn cegamente. Normalize um subconjunto limitado em tokens do OpenClaw e derive o restante em um auxiliar.
Tokens importados diretamente
De cada bloco de modo do tweakcn:
backgroundforegroundcardcard-foregroundpopoverpopover-foregroundprimaryprimary-foregroundsecondarysecondary-foregroundmutedmuted-foregroundaccentaccent-foregrounddestructivedestructive-foregroundborderinputringradius
De cssVars.theme compartilhado quando presente:
font-sansfont-mono
Se um bloco de modo sobrescrever font-sans, font-mono ou radius, o valor local do modo vence.
Tokens derivados para o OpenClaw
O importador deriva variáveis exclusivas do OpenClaw a partir das cores base importadas:
--bg-accent--bg-elevated--bg-hover--panel--panel-strong--panel-hover--chrome--chrome-strong--text--text-strong--chat-text--muted--muted-strong--accent-hover--accent-muted--accent-subtle--accent-glow--focus--focus-ring--focus-glow--secondary--secondary-foreground--danger--danger-muted--danger-subtle
As regras de derivação vivem em um auxiliar puro para que possam ser testadas independentemente. As fórmulas exatas de mistura de cores são um detalhe de implementação, mas o auxiliar deve satisfazer duas restrições:
- preservar contraste legível próximo à intenção do tema importado
- produzir saída estável para o mesmo payload importado
Tokens ignorados na v1
Estes tokens do tweakcn são ignorados intencionalmente na primeira versão:
chart-*sidebar-*font-serifshadow-*tracking-*letter-spacingspacing
Isso mantém o escopo nos tokens de que a Interface de Controle atual realmente precisa.
Fontes
Strings de pilha de fontes são importadas se presentes, mas o OpenClaw não carrega ativos de fonte remotos na v1. Se a pilha importada referenciar fontes indisponíveis no navegador, o comportamento normal de fallback se aplica.
Comportamento de falha
Importações incorretas devem falhar fechadas.
- Formato de URL inválido: mostrar erro de validação inline, não buscar.
- Host ou formato de caminho sem suporte: mostrar erro de validação inline, não buscar.
- Falha de rede, resposta não OK ou JSON malformado: mostrar erro inline, manter o payload armazenado atual intacto.
- Falha de esquema ou blocos claro/escuro ausentes: mostrar erro inline, manter o payload armazenado atual intacto.
- Ação de limpeza:
- remove o payload personalizado armazenado
- remove o conteúdo da tag de estilo personalizada gerenciada
- se
customestiver ativo, troca a família de tema de volta paraclaw
- Payload personalizado armazenado inválido no primeiro carregamento:
- ignora o payload armazenado
- não emite CSS personalizado
- se a família de tema persistida era
custom, volta paraclaw
Em nenhum momento uma importação com falha deve deixar o documento ativo com variáveis CSS personalizadas parciais aplicadas.
Arquivos esperados para mudar na implementação
Arquivos principais:
ui/src/ui/theme.tsui/src/ui/storage.tsui/src/ui/app-settings.tsui/src/ui/views/config.tsui/src/ui/views/config-quick.tsui/src/styles/base.css
Novos auxiliares prováveis:
ui/src/ui/custom-theme.ts
Testes:
ui/src/ui/app-settings.test.tsui/src/ui/storage.node.test.tsui/src/ui/views/config.browser.test.ts- novos testes focados para análise de URL e normalização de payload
Testes
Cobertura mínima de implementação:
- analisar URL de link de compartilhamento para obter o id de tema do tweakcn
- normalizar
/themes/{id}e/r/themes/{id}para a URL de busca - rejeitar hosts sem suporte e ids malformados
- validar o formato do payload do tweakcn
- mapear um payload válido do tweakcn para mapas de tokens claros e escuros normalizados do OpenClaw
- carregar e salvar o payload personalizado em configurações locais do navegador
- resolver
customparalight,darkesystem - desabilitar a seleção de
Customquando nenhum payload existir - aplicar o tema importado imediatamente quando
customjá estiver ativo - voltar para
clawquando o tema personalizado ativo for limpo
Alvo de verificação manual:
- importar um tema conhecido do tweakcn em Configurações
- alternar entre
light,darkesystem - alternar entre
custome as famílias integradas - recarregar a página e confirmar que o tema personalizado importado persiste localmente
Observações de lançamento
Este recurso é intencionalmente pequeno. Se os usuários depois pedirem múltiplos temas importados, renomeação, exportação ou sincronização entre dispositivos, trate isso como um design subsequente. Não pré-construa uma abstração de biblioteca de temas nesta implementação.