Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions .claude/skills/check-module/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions module.json
Original file line number Diff line number Diff line change
Expand Up @@ -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" },
Expand Down
5 changes: 4 additions & 1 deletion src/components/ui/input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion src/components/ui/select.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down
7 changes: 6 additions & 1 deletion src/components/ui/tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
)}
Expand Down