diff --git a/.claude/skills/check-module/SKILL.md b/.claude/skills/check-module/SKILL.md index 6294d70..b8777e4 100644 --- a/.claude/skills/check-module/SKILL.md +++ b/.claude/skills/check-module/SKILL.md @@ -148,6 +148,60 @@ fi Necessario para `platform-setup` rodar. Se vazio ou ainda contem `stage.cora.team` num modulo prod, sinalize. +### 2.9 — `accent` no enum V2 (severity: error quando declarado, warn quando ausente) + +O shell aceita 7 valores fechados pro `accent`. Se declarado fora desse set, o `POST /api/modules` rejeita. Se ausente, o shell auto-infere via hash deterministico do `id` — sem custo, mas vale informar pro dev: + +```bash +ACCENT=$(jq -r '.accent // ""' module.json) +case "$ACCENT" in + "") + PROBLEMS+=("aviso: accent ausente — shell vai auto-inferir do hash do id (estavel, mas sem identidade declarada)") + ;; + coral|amber|violet|teal|blue|rose|lime) + : # ok + ;; + *) + PROBLEMS+=("erro: accent \"$ACCENT\" fora do enum (use coral|amber|violet|teal|blue|rose|lime)") + ;; +esac +``` + +### 2.10 — `icon` ausente (severity: warn) + +`icon` e nome lucide em kebab-case. Sem ele, o shell usa o default do `group`. Se ambos `accent` e `icon` ausentes, modulos do mesmo grupo ficam visualmente indistinguiveis na grid: + +```bash +ICON=$(jq -r '.icon // ""' module.json) +if [ -z "$ICON" ]; then + PROBLEMS+=("aviso: icon ausente — chip vai usar default do group \"$GROUP\"") +fi +``` + +### 2.11 — Hardcoded colors no `src/` (severity: warn) + +Modulos devem consumir CSS custom properties (`var(--token)`) em vez de fixar hex. Cores fixas escapam quando o shell troca de tema (light/dark) ou quando o palette V2 evolui. + +O grep filtra falsos positivos comuns: comentarios (`//`, `*`, `/*`), declaracoes de CSS var (linhas que comecam com `--token:`), e qualquer linha que ja referencia `var(--`. O total de ocorrencias e contado **antes** do truncate, e ate 5 amostras vao no report pra dar contexto: + +```bash +HARDCODED=$(grep -rnE "#[0-9a-fA-F]{3,8}\b|rgb\(|rgba\(" src/ \ + --include='*.ts' --include='*.tsx' --include='*.css' 2>/dev/null \ + | grep -v "var(--" \ + | grep -vE "^[^:]+:\s*//|^[^:]+:\s*\*|^[^:]+:\s*/\*" \ + | grep -vE "^[^:]+:\s*--[a-zA-Z0-9_-]+:") + +if [ -n "$HARDCODED" ]; then + TOTAL=$(echo "$HARDCODED" | wc -l) + PROBLEMS+=("aviso: $TOTAL cor(es) hardcoded em src/ — use var(--token) para respeitar light/dark e o palette V2") + while IFS= read -r line; do + PROBLEMS+=(" $line") + done <<< "$(echo "$HARDCODED" | head -5)" +fi +``` + +> `grep -n` traz numero da linha pra cada amostra ficar acionavel. Falsos positivos ainda sao possiveis (rgba em sombras decorativas, hex em SVG icons inline) — por isso e aviso, nao erro. + ## Passo 3 — Reportar Imprima um sumario humano em PT-BR: diff --git a/README.md b/README.md index bff2639..25bba99 100644 --- a/README.md +++ b/README.md @@ -90,6 +90,47 @@ platform/configs/ # Values por ambiente (stage, prod) module.json # Metadados para auto-registro ``` +## Identidade visual — accent e icon + +Cada módulo aparece no grid da Home do shell como um **card** com um chip colorido (44×44) e um ícone. Dois campos opcionais no `module.json` controlam isso: + +```json +"accent": "coral", +"icon": "shield", +``` + +**accent** — cor do chip. Um destes 7 valores: + +| accent | hue (aprox.) | uso típico | +|---|---|---| +| `coral` | vermelho coral | ações principais / regulatório | +| `amber` | âmbar | configuração / toggles | +| `violet` | roxo | orquestração / fluxo | +| `teal` | verde-azul | aprendizagem / coleta | +| `blue` | azul | ferramentas / API | +| `rose` | rosa | suporte / pessoas | +| `lime` | verde-lima | dados / analytics | + +**icon** — nome do ícone [lucide](https://lucide.dev/icons/) em kebab-case (ex.: `shield`, `git-branch`, `database`, `book-open`, `key`). + +### Os dois são opcionais + +Se você não declarar **accent**, o shell pega 1 dos 7 valores via hash determinístico do `id` do módulo — mesmo `id` sempre cai na mesma cor, em qualquer sessão e dispositivo. + +Se você não declarar **icon**, o shell usa um default por `group`: + +| group | icon default | +|---|---| +| `operations` | `layers` | +| `finance` | `wallet` | +| `customer-support` | `life-buoy` | +| `analytics` | `chart-line` | +| `settings` | `settings` | + +> Sem custo cognitivo no dia 1 — declare quando quiser dar identidade visual ao módulo, deixe vazio quando estiver tudo bem com o auto. + +A skill `/check-module` valida se o `accent` declarado está no enum e avisa quando os dois estão ausentes (informativo — não bloqueia deploy). + ## Sidebar — Navegação e Perspectivas Por padrão o `module.json` declara um **menu único** no campo `navigation` (caso da maioria dos módulos): diff --git a/module.json b/module.json index 5a695a5..7d9666a 100644 --- a/module.json +++ b/module.json @@ -6,6 +6,8 @@ "version": "0.1.0", "group": "operations", "order": 20, + "accent": "coral", + "icon": "shield", "permissions": ["my-module:read", "my-module:write"], "navigation": [ { "label": "Overview", "path": "/my-module", "requiredPermission": "my-module:read" }, diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx index f1124ae..a27ca77 100644 --- a/src/components/ui/input.tsx +++ b/src/components/ui/input.tsx @@ -8,7 +8,10 @@ function Input({ className, type, ...props }: React.ComponentProps<"input">) { type={type} data-slot="input" className={cn( - "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30", + // Input bg follows --card so it stands off the page bg in the + // shell's V2 cream theme. The shadcn default (bg-transparent) + // blends into the cream and looks invisible. + "h-9 w-full min-w-0 rounded-md border border-input bg-card px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm", "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50", "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40", className diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx index c0dc712..5a8f407 100644 --- a/src/components/ui/select.tsx +++ b/src/components/ui/select.tsx @@ -37,7 +37,8 @@ function SelectTrigger({ data-slot="select-trigger" data-size={size} className={cn( - "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-transparent px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 dark:bg-input/30 dark:hover:bg-input/50 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", + // Trigger bg follows --card to match the Input — same reasoning. + "flex w-fit items-center justify-between gap-2 rounded-md border border-input bg-card px-3 py-2 text-sm whitespace-nowrap shadow-xs transition-[color,box-shadow] outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:cursor-not-allowed disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[placeholder]:text-muted-foreground data-[size=default]:h-9 data-[size=sm]:h-8 *:data-[slot=select-value]:line-clamp-1 *:data-[slot=select-value]:flex *:data-[slot=select-value]:items-center *:data-[slot=select-value]:gap-2 hover:bg-muted dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground", className )} {...props} diff --git a/src/components/ui/tabs.tsx b/src/components/ui/tabs.tsx index b463afd..9251a28 100644 --- a/src/components/ui/tabs.tsx +++ b/src/components/ui/tabs.tsx @@ -66,7 +66,12 @@ function TabsTrigger({ className={cn( "relative inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap text-foreground/60 transition-all group-data-[orientation=vertical]/tabs:w-full group-data-[orientation=vertical]/tabs:justify-start hover:text-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1 focus-visible:outline-ring disabled:pointer-events-none disabled:opacity-50 group-data-[variant=default]/tabs-list:data-[state=active]:shadow-sm group-data-[variant=line]/tabs-list:data-[state=active]:shadow-none dark:text-muted-foreground dark:hover:text-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "group-data-[variant=line]/tabs-list:bg-transparent group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:border-transparent dark:group-data-[variant=line]/tabs-list:data-[state=active]:bg-transparent", - "data-[state=active]:bg-background data-[state=active]:text-foreground dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 dark:data-[state=active]:text-foreground", + // Active tab uses --card (white panel) so it stands off the + // shell's V2 cream page bg. The shadcn default (bg-background) + // matches the page bg in the new theme. Shadow uses Tailwind's + // shadow-sm utility so the template stays free of literal rgba + // values that /check-module's hardcoded-color check would flag. + "data-[state=active]:bg-card data-[state=active]:text-foreground data-[state=active]:shadow-sm", "after:absolute after:bg-foreground after:opacity-0 after:transition-opacity group-data-[orientation=horizontal]/tabs:after:inset-x-0 group-data-[orientation=horizontal]/tabs:after:bottom-[-5px] group-data-[orientation=horizontal]/tabs:after:h-0.5 group-data-[orientation=vertical]/tabs:after:inset-y-0 group-data-[orientation=vertical]/tabs:after:-right-1 group-data-[orientation=vertical]/tabs:after:w-0.5 group-data-[variant=line]/tabs-list:data-[state=active]:after:opacity-100", className )}