Use when writing, reviewing, or refactoring Slint (.slint) UI files — covers component design, property bindings, callbacks, state management, layouts, accessibility, translations, theming, and integration with Rust backend
Use when writing, reviewing, or refactoring Slint (.slint) UI files — covers component design, property bindings, callbacks, state management, layouts, accessibility, translations, theming, and integration with Rust backend
LEI — esta skill NÃO basta sozinha: invoque a skill de UI/UX antes
Antes de escrever/alterar qualquer .slint, invoque TAMBÉM claude-plugin:ux-ui (design/UX). Esta skill cobre a MECÂNICA do Slint (bindings, layouts, @tr, globals); ela não diz nada sobre hierarquia visual, densidade, contraste, estados vazio/erro, ou se a tela funciona pro usuário. As duas são gate, não uma ou outra.
Why: invocar só a de Slint produz tela que compila e renderiza, mas com decisão visual inventada por mim — exatamente o que a LEI de UI/Slint do CLAUDE.md proíbe ("PROIBIDO supor/inventar layout"). Já aconteceu: overlay inteiro construído com slint-best-practices só, sem a skill de UX.
How to apply: trabalho de tela → claude-plugin:ux-ui + slint:slint + esta skill, ANTES da primeira linha; depois renderize com tools/slint-render e confira o PNG antes de dizer "pronto".
Princípios gerais de UI (responsividade, separação business/presentation, zero coupling) vivem em openrig-code-quality. As regras Slint-específicas do projeto:
Quality Gate — compartilhado (issue #482)
xgodev/quality-gate
Gate único mantido fora do repo (igual local e CI). Compara 6 métricas do PR vs origin/develop — se algum erro de compilação Slint novo entrar, build errors aumentam e o gate falha. Antes de qualquer git push (ou via skill claude-plugin:quality-gate):
Falha em CI vira sticky comment + request-changes formal. Detalhes em docs/development/quality-gate.md.
File Size — 500 lines per .slint (hard cap)
./scripts/validate.sh enforces ≤ 500 lines for every .slint. Se um arquivo passa, dividir antes de adicionar mais qualquer coisa. Esta é a operacionalização Slint do princípio "one responsibility per file" do openrig-code-quality.
NUNCA sed -i em arquivos .slint
sed -i em macOS/BSD pode esvaziar um .slint por causa de issues de encoding/locale (vimos isso quebrar arquivos de UI em outras issues). Use o Edit tool sempre.
❌ sed -i '' 's/old/new/g' app.slint
// PERIGO: pode esvaziar o arquivo
✅ Edit tool com old_string/new_string
@image-url() é compile-time — sem strings dinâmicas
@image-url() resolve no momento da compilação Slint. Não aceita variável runtime. Para selecionar imagem por model_id ou brand, use ternary chain:
A consequência prática para o catálogo OpenRig: cada novo brand exige tocar a chain de ternários nos componentes que renderizam a logo. Isso é uma exceção autorizada ao "zero coupling" — Slint não tem outra forma. Centralize a chain em UM componente (BrandLogo.slint) para minimizar pontos de toque.
Não hardcode cores/fontes por model_id em Slint
Princípio em openrig-code-quality (separation of concerns). Operacionalização Slint:
❌ if root.model_id == "marshall_jcm_800": Rectangle { background: #6c2a1a; }
// WRONG: cor hardcoded no Slint por model_id
✅ private property <color> panel-bg:
root.block-model-options[index].panel_bg;
// CORRETO: cor vem de visual_config (UI layer Rust), exposto como property Slint
Painel editor genérico — sem lógica por effect_type
O BlockEditorPanel deve renderizar qualquer effect_type baseado no schema, não em if effect_type == "preamp". Adicionar um effect_type novo NÃO deve exigir mudança no Panel.
1. Estrutura de Projeto
Separar código, UI e assets em diretórios distintos:
Regra: Nenhuma lógica de negócio dentro de .slint. Slint é declarativo — computações pertencem ao Rust.
2. Propriedades — Acesso e Direção
Sempre declarar acesso explícito em componentes:
Modificador
Uso
in
Dado vem de fora (pai → filho)
out
Dado vai para fora (filho → pai)
in-out
Bidirecional (usar com cautela)
private
Interno ao componente (default)
component MyButton {
in property <string> label; // pai configura
out property <bool> pressed; // pai observa
private property <bool> hovered; // interno
}
Evitar in-out sem necessidade — bidirecionalidade cria acoplamento difícil de rastrear.
3. Bindings Reativos
Bindings se re-avaliam automaticamente quando dependências mudam. Nunca atribuir manualmente o que pode ser um binding.
// ✅ Binding reativo — atualiza automaticamente
Text { text: root.count > 0 ? "Items: \{root.count}" : "Empty"; }
// ❌ Evitar — imperativo, perde reatividade
Text {
text: "Items";
// lógica imperativa via callback para atualizar text = ...
}
Regra: Prefira expressões ternárias e bindings declarativos sobre callbacks que modificam estado.
4. Callbacks — Direção e Nomenclatura
component SearchBar {
callback search-requested(string); // filho notifica pai
callback clear-requested();
// ❌ Evitar: callback que retorna dado para o filho
// callback fetch-data() -> [DataModel]; // acoplamento invertido
}
Callbacks fluem de filho para pai (eventos)
Dados fluem de pai para filho (propriedades in)
Use <=> para two-way binding entre propriedades de mesmo nível
// ✅ Correto — permite reordenação pelo tradutor
Text { text: @tr("Hello, {}", name); }
// ❌ Errado — concatenação dificulta tradução
Text { text: @tr("Hello, ") + name; }
// ❌ Esqueceu o @tr
Text { text: "Save Project"; }
Toda string visível ao usuário deve usar @tr("...").
9. Globals
Use global para estado compartilhado entre componentes sem prop-drilling:
export global AppTheme {
out property <color> accent: #4CAF50;
out property <length> spacing: 8px;
}
// Uso em qualquer componente
Rectangle { background: AppTheme.accent; }
Globals são singletons — ideal para tema, configurações, estado de app